当前位置: 首页> 财经> 访谈 > 揭秘Java I/O体系-从装饰者模式到Reader、Writer流

揭秘Java I/O体系-从装饰者模式到Reader、Writer流

时间:2025/8/23 9:10:09来源:https://blog.csdn.net/lizhong2008/article/details/139361500 浏览次数:0次

作为资深Java开发者,相信大家对Java的I/O体系都不会陌生。毕竟,I/O操作无处不在,是我们与外部世界进行交互的关键桥梁。今天,就让我带大家领略一下Java I/O体系的精髓所在!


我们将从装饰者模式的设计理念出发,深入分析InputStream/OutputStream和Reader/Writer这两大流体系的工作机制。最后,通过详细的代码示例,让大家亲身体会Java I/O操作的魅力所在。话不多说,让我们直接开始今天的分享吧!


一、装饰者模式的应用


作为23种设计模式之一,装饰者模式在Java I/O体系中得到了大量应用。

它的核心思想是:在不改变原有对象的基础上,动态地给对象添加一些附加职责。这种做法比继承更有弹性,体现了"对修改关闭,对扩展开放"的设计原则。

Java I/O体系之所以具有极高的灵活性和可扩展性,正是因为广泛使用了装饰者模式。

以FileInputStream为例,它只负责读取文件数据流的基本功能,而读取指定字节范围内的数据、支持自动按行读取等高级功能,则由FileInputStream的"装饰类"来提供。

// 创建文件输入流
FileInputStream fis = new FileInputStream("data.txt");
// 装饰为BufferedInputStream以及DataInputStream
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);// 读取基本数据类型
int num = dis.readInt();  
boolean flag = dis.readBoolean();
// ...

1、InputStream/OutputStream详解

(1)、InputStream

InputStream 是用于从不同数据源读取字节的抽象基类。它提供了一个基本的接口,用于读取字节数据。InputStream 的子类包括:

  • FileInputStream:用于读取文件中的字节。

  • ByteArrayInputStream:用于从字节数组中读取字节。

  • PipedInputStream:用于从管道中读取字节。


(2)、OutputStream

OutputStream 是用于向不同数据源写入字节的抽象基类。它提供了一个基本的接口,用于写入字节数据。OutputStream 的子类包括:

  • FileOutputStream:用于向文件写入字节。
  • ByteArrayOutputStream:用于向字节数组写入字节。
  • PipedOutputStream:用于向管道写入字节。

(3)、使用 InputStream 和 OutputStream 的示例

下面演示如何使用 FileInputStreamFileOutputStream 来读取和写入文件:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class StreamExample {public static void main(String[] args) {// 定义要读取的文件路径和要写入的文件路径String sourceFilePath = "source.txt";String destinationFilePath = "destination.txt";try (FileInputStream fis = new FileInputStream(sourceFilePath);FileOutputStream fos = new FileOutputStream(destinationFilePath)) {// 定义一个字节数组来临时存储读取的数据byte[] buffer = new byte[1024];int bytesRead;// 读取源文件并写入目标文件while ((bytesRead = fis.read(buffer)) != -1) {fos.write(buffer, 0, bytesRead);}System.out.println("文件复制完成。");} catch (IOException e) {e.printStackTrace();}}
}

在这个示例中,我们创建了两个文件流对象:FileInputStream 用于读取 source.txt 文件,而 FileOutputStream 用于向 destination.txt 文件写入数据。

我们使用 read() 方法从 FileInputStream 中读取数据到一个字节数组 buffer 中,并使用 write() 方法将这些数据写入到 FileOutputStream 中。

注意,我们使用了 try-with-resources 语句来自动关闭流,这是处理 InputStreamOutputStream 的推荐方式,因为它可以确保即使发生异常也会正确关闭资源。

这个示例只是一个简单的例子,InputStreamOutputStream 的子类可以用于更复杂的场景,如网络通信、数据压缩、加密等。

Java提供了许多InputStream/OutputStream的具体实现类,用于满足不同场景下的读写需求。

例如ByteArrayInputStream可读写内存字节数组、ObjectOutputStream可直接序列化Java对象等。它们均遵循装饰者模式,可以相互装饰,形成链式调用。


2、ByteArrayInputStream/ObjectOutputStream详解

ByteArrayInputStreamObjectOutputStream 是 Java 中用于处理数据流的类。ByteArrayInputStream 允许你读取内存中的字节数组,而 ObjectOutputStream 用于将 Java 对象序列化到输出流中。


下面是分别演示如何使用 ByteArrayInputStreamObjectOutputStream

(1)、使用 ByteArrayInputStream 读取内存中的字节数组

这个示例展示了如何将一个字符串转换为字节数组,并使用 ByteArrayInputStream 来读取这个字节数组。

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;public class ByteArrayInputStreamExample {public static void main(String[] args) {try {// 创建一个字符串String originalString = "Hello, this is a test string!";// 将字符串转换为字节数组byte[] byteArray = originalString.getBytes();// 创建 ByteArrayInputStream 对象ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray);// 读取字节数组int data = 0;while ((data = inputStream.read()) != -1) {System.out.print((char) data);}System.out.println("\nFinished reading.");} catch (IOException e) {e.printStackTrace();}}
}

在这个示例中,我们创建了一个 ByteArrayInputStream 来读取内存中的字节数组,并打印出每个字节对应的字符。


(2)、使用 ObjectOutputStream 序列化 Java 对象

这个示例展示了如何使用 ObjectOutputStream 来序列化一个简单的 Java 对象。

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;public class ObjectOutputStreamExample {public static void main(String[] args) {try {// 创建一个简单的 Java 对象MyObject myObject = new MyObject("Kimi", 2024);// 创建 FileOutputStream 对象FileOutputStream fileOutputStream = new FileOutputStream("myObject.ser");// 创建 ObjectOutputStream 对象ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);// 序列化对象objectOutputStream.writeObject(myObject);// 清理资源objectOutputStream.close();fileOutputStream.close();System.out.println("Object has been serialized.");} catch (IOException e) {e.printStackTrace();}}
}class MyObject implements java.io.Serializable {private static final long serialVersionUID = 1L;private String name;private int year;public MyObject(String name, int year) {this.name = name;this.year = year;}// Getters and setterspublic String getName() {return name;}public void setName(String name) {this.name = name;}public int getYear() {return year;}public void setYear(int year) {this.year = year;}
}

在这个示例中,我们创建了一个 MyObject 类,它实现了 Serializable 接口,这意味着它可以被序列化。我们使用 ObjectOutputStreamMyObject 实例序列化到一个文件中。


3、Reader/Writer详解

(1)、Reader

Reader 是用于读取字符流的抽象基类。它使用 char 类型的缓冲区,并且可以指定字符编码。Reader 的子类包括:

  • FileReader:用于读取字符文件。

  • CharArrayReader:用于从字符数组中读取字符。

  • StringReader:用于从字符串中读取字符。


(2)、Writer

Writer 是用于写入字符流的抽象基类。它使用 char 类型的缓冲区,并且可以指定字符编码。Writer 的子类包括:

  • FileWriter:用于写入字符到文件。

  • CharArrayWriter:用于向字符数组写入字符。

  • StringWriter:用于向字符串写入字符。


(3)、使用 Reader 和 Writer 的示例

下面演示如何使用 FileReaderFileWriter 来读取和写入文本文件:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;public class ReaderWriterExample {public static void main(String[] args) {// 定义要读取的文件路径和要写入的文件路径String sourceFilePath = "source.txt";String destinationFilePath = "destination.txt";try (FileReader fr = new FileReader(sourceFilePath);FileWriter fw = new FileWriter(destinationFilePath)) {char[] buffer = new char[1024];int charsRead;// 读取源文件并写入目标文件while ((charsRead = fr.read(buffer)) != -1) {fw.write(buffer, 0, charsRead);}System.out.println("文件复制完成。");} catch (IOException e) {e.printStackTrace();}}
}

在这个示例中,我们创建了两个字符流对象:FileReader 用于读取 source.txt 文件中的字符,而 FileWriter 用于向 destination.txt 文件写入字符。

我们使用 read() 方法从 FileReader 中读取字符到一个字符数组 buffer 中,并使用 write() 方法将这些字符写入到 FileWriter 中。

注意,我们使用了 try-with-resources 语句来自动关闭流,这是处理 ReaderWriter 的推荐方式,因为它可以确保即使发生异常也会正确关闭资源。

这个示例只是一个简单的例子,ReaderWriter 的子类可以用于更复杂的场景,如网络通信、字符编码转换、国际化等。

与InputStream/OutputStream类似, Java也为Reader/Writer提供了多种具体实现类,如StringReader可读写字符串、CharArrayWriter可写出字符数组等。同时,它们也使用了装饰者模式,支持无缝组合和扩展。


4、StringReader/CharArrayWriter详解

StringReaderCharArrayWriter 是 Java 中的两种字符流类,分别用于从字符串读取字符和向字符数组写入字符。


下面是演示使用 StringReaderCharArrayWriter 的示例。


(1)、使用 StringReader 读取字符串

StringReader 类是 java.io 包中的一个类,它允许从字符串中读取字符流。

import java.io.StringReader;
import java.io.IOException;public class StringReaderExample {public static void main(String[] args) {String text = "Hello, World!";StringReader stringReader = new StringReader(text);try {int i;while ((i = stringReader.read()) != -1) {// 打印每个字符System.out.print((char) i);}} catch (IOException e) {e.printStackTrace();} finally {// 总是关闭流stringReader.close();}}
}

(2)、使用 CharArrayWriter 写入字符数组

CharArrayWriter 类是 java.io 包中的一个类,它允许将字符写入内部字符数组。

import java.io.IOException;
import java.io.CharArrayWriter;public class CharArrayWriterExample {public static void main(String[] args) {CharArrayWriter charArrayWriter = new CharArrayWriter();try {// 写入字符串到CharArrayWritercharArrayWriter.write("Hello, ");charArrayWriter.write("World!");// 将CharArrayWriter的内容转换为字符串String result = charArrayWriter.toString();System.out.println(result);// 获取字符数组char[] chars = charArrayWriter.toCharArray();System.out.println("字符数组内容:");for (char c : chars) {System.out.print(c);}} catch (IOException e) {e.printStackTrace();}}
}

StringReaderExample 示例中,我们创建了一个 StringReader 对象,并使用它来读取字符串 text 中的字符。我们通过循环调用 read() 方法来逐个字符地读取,并打印每个字符。

CharArrayWriterExample 示例中,我们创建了一个 CharArrayWriter 对象,并使用它来写入字符串。我们调用 write() 方法来写入字符。之后,我们使用 toString() 方法将 CharArrayWriter 中的内容转换为字符串,并打印出来。我们还可以使用 toCharArray() 方法获取内部字符数组,并打印数组中的字符。

请注意,尽管 CharArrayWriter 可以写入字符,但它通常用于收集字符,然后可以通过 toString().toCharArray() 方法一次性获取所有字符。而 StringReader 则用于从字符串中逐个读取字符,适合于字符流的读取操作。


二、字节流与字符流的区别


字节流和字符流是 Java I/O 流中两种不同类型的流,它们在处理数据时有不同的特点和用途。

1、字节流(Byte Streams)
  • 字节流主要用于处理二进制数据,如图片、视频、可执行文件等。

  • 字节流可以处理任何类型的数据,因为它只读取和写入字节。

  • 字节流不关心字符编码,因此它不涉及字符集转换。

  • 字节流的基类是 InputStreamOutputStream


2、字符流(Character Streams)
  • 字符流主要用于处理字符数据,即文本数据。

  • 字符流使用字符集(如 UTF-8, GBK 等)来编码和解码字符。

  • 字符流可以自动处理字符编码转换,适合文本文件的读写。

  • 字符流的基类是 ReaderWriter


3、字节流与字符流的区别:
  1. 数据类型:字节流处理原始字节数据,字符流处理字符数据。

  2. 编码:字节流不涉及编码转换,字符流涉及字符编码和解码。

  3. 用途:字节流适合二进制文件,字符流适合文本文件。

  4. 效率:对于文本数据,字符流可能因为编码转换而效率较低;对于二进制数据,字节流更高效。


4、字节流示例:复制文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class ByteStreamExample {public static void main(String[] args) {String sourceFilePath = "source.bin";String destinationFilePath = "destination.bin";try (FileInputStream fis = new FileInputStream(sourceFilePath);FileOutputStream fos = new FileOutputStream(destinationFilePath)) {int byteRead;while ((byteRead = fis.read()) != -1) {fos.write(byteRead);}} catch (IOException e) {e.printStackTrace();}}
}

这个示例展示了如何使用字节流来复制一个二进制文件。


5、字符流示例:复制文本文件
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;public class CharStreamExample {public static void main(String[] args) {String sourceFilePath = "source.txt";String destinationFilePath = "destination.txt";try (FileReader fr = new FileReader(sourceFilePath);FileWriter fw = new FileWriter(destinationFilePath)) {char[] buffer = new char[1024];int charsRead;while ((charsRead = fr.read(buffer)) != -1) {fw.write(buffer, 0, charsRead);}} catch (IOException e) {e.printStackTrace();}}
}

这个示例展示了如何使用字符流来复制一个文本文件。

在这两个示例中,我们使用了 try-with-resources 语句来自动管理资源,确保文件流在使用后能够被正确关闭。字节流示例中,我们逐字节读取和写入数据;而在字符流示例中,我们使用字符数组作为缓冲区,逐字符读取和写入数据。字符流示例中还隐含了字符编码的处理,这是字符流的一个优势。


总的来说,字节流适合用于处理二进制数据,如图片、视频等媒体文件。而字符流则更擅长处理纯文本数据。在具体使用时,如果明确知道只处理纯文本数据,则优先考虑使用Reader/Writer,否则使用InputStream/OutputStream。


不过,对于纯文本数据,字节流和字符流二者是可以相互转换的。InputStreamReader可将字节流转换为字符流,而OutputStreamWriter则相反。它们的作用是编码/解码文本数据,以实现文本数据和纯字节之间的相互转换。


三、总结


通过上述讲解,相信大家已经对Java I/O体系有了更深入的理解。我们首先剖析了装饰者模式在其中的巧妙应用,随后分别介绍了InputStream/OutputStream和Reader/Writer两大流体系的工作原理。通过示例代码,我们也亲身体会了Java I/O操作的便利之处。


当然,本文只是对Java I/O体系的一个概览。在实际开发过程中,我们还需要注意流的正确使用、异常处理、性能优化等诸多细节问题。而且,在Java 7之后,官方还推出了NIO2的新I/O框架,它提供了更现代化、高效的文件系统操作方式,也是我们后续值得学习的重点。


最后,正如开篇时所说,我将为大家留一个小小的悬念。这里提出一个问题:Java中的I/O流是否真的需要手动关闭?如果不手动关闭,会发生什么?欢迎各位读者朋友思考并在下期与我分享你的答案!更多Java技术分享,敬请继续关注我的博客,下期不见不散!


关键字:揭秘Java I/O体系-从装饰者模式到Reader、Writer流

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: