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


作为资深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技术分享,敬请继续关注我的博客,下期不见不散!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/19846.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

备忘录--

备忘录 vue新建项目&#xff0c;body 大小占不了全屏 // 解决问题 // public/index.html 更改基础样式 html,body{height: 100%width: 100%&#xff1b;margin: 0;border: 0;padding: 0; }Element Plus 经典布局 参考 Element Plus 官网 <template><div class"…

移动端性能测试(android/ios)

solox官网 https://github.com/smart-test-ti/SoloX solox简介 实时收集android/ios性能的工具&#xff0c;Android设备无需Root&#xff0c;iOS设备无需越狱。有效解决Android和iOS性能的测试和分析挑战。 solox安装 环境准备 python安装3.10以上的 python官网下载地址…

JavaScript 创建新节点的方法

在 JavaScript 中&#xff0c;可以使用 document.createElement() 方法来创建新的节点。该方法接受一个字符串参数&#xff0c;表示要创建的节点类型&#xff0c;如 "div"、"p" 等。 创建一个新节点的基本步骤如下&#xff1a; 使用 document.createElem…

C# 控制流语句详解

C#提供了多种控制流语句&#xff0c;允许开发者根据不同的条件执行不同的代码分支。本篇博客将详细介绍if-else、switch、while、do-while、for、foreach循环&#xff0c;以及break、continue、goto、return语句。 if-else 语句 if-else语句用于基于条件执行不同的代码分支。…

Elasticsearch 分布式搜索、分布式索引、分布式存储详解

Elasticsearch 分布式搜索、分布式索引、分布式存储详解 Elasticsearch 分布式搜索、分布式索引、分布式存储详解一、引言二、Elasticsearch 分布式搜索详解1. 分片和副本2. 查询过程中的搜索流程3. 搜索结果的合并与排序 三、Elasticsearch 分布式索引详解四、Elasticsearch 分…

Mac终端安装brew(亲测有效)

找了好久&#xff0c;七七八八都是报访问不到github的问题&#xff0c;下面这个亲测有效&#xff0c;方便快捷&#xff0c;可以试试 # 安装脚本&#xff0c;直接执行&#xff0c;按照提示输入即可 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/ma…

数据结构栈(C语言Java语言的实现)相关习题

文章目录 栈概念以及代码实现例题[232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/)[1614. 括号的最大嵌套深度](https://leetcode.cn/problems/maximum-nesting-depth-of-the-parentheses/)[234. 回文链表](https://leetcode.cn/problems/pal…

鸿蒙ArkTS声明式开发:跨平台支持列表【透明度设置】 通用属性

透明度设置 设置组件的透明度。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版…

Vue3-Vite-ts 前端生成拓扑图,复制即用

完整代码&#xff0c;复制即可用&#xff0c;样式自调 试过 jointjs dagre-d3 vis&#xff0c;好用一点 方法1&#xff1a;Vis.js npm install vis-network <template><div id"mynetwork" class"myChart" :style"{width: 100%, height: 9…

【UE5:CesiumForUnreal】——加载无高度地形数据

目录 1.实现目的 2.数据准备 2.1下载数据 2.2 数据切片 3.加载无地形数据 1.实现目的 在CesiumForUnreal插件中&#xff0c;我们加载地图和地形图层之后&#xff0c;默认都是加载的带有高程信息的地形数据&#xff0c;在实际的项目和开发中&#xff0c;有时候我们需要加载无…

lipo制作通用版本静态库

文章目录 目的了解多架构的maclipo如何利用lipo编译通用版本静态库lipo 命令整理扩展目的 主要是使用lipo命令在macOS上创建通用版本的静态库(.a文件),来支持多种架构,如arm64,x86_64。 学习目的: 了解mac 不同架构arm64, x86_64了解lipo命令了解多架构的mac 随着appl…

数据挖掘 | 实验三 决策树分类算法

文章目录 一、目的与要求二、实验设备与环境、数据三、实验内容四、实验小结 一、目的与要求 1&#xff09;熟悉决策树的原理&#xff1b; 2&#xff09;熟练使用sklearn库中相关决策树分类算法、预测方法&#xff1b; 3&#xff09;熟悉pydotplus、 GraphViz等库中决策树模型…

【期末速成】——计算机组成原理(1)概述

目录 一、什么是计算机的组成 二、冯诺依曼体系结构计算机的特点 三、计算机系统的层次结构 四、机器语言、汇编语言、高级语言, 五、 编译程序、解释程序、汇编程序 六、已知主频、CPI计算程序运行时间 一、什么是计算机的组成 计算机的组成可以分为五个部件和两个信息…

数据通信中,证书的作用?

标签: 证书; 证书的作用;数据通信; 在计算机信息安全领域,证书(Certificate)起着至关重要的作用,主要用于确保通信的安全性、完整性和可信性。以下是证书的主要作用及其相关概念的解释: 1. 身份验证 证书的一个主要作用是验证身份。在网络通信中,证书用于确认通信…

代码随想录算法训练营第二十四天|

[LeetCode] 77. 组合 [LeetCode] 77. 组合 文章解释 [LeetCode] 77. 组合 视频解释 题目: 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4…

针对固定定位/绝对定位元素实现水平垂直居中的方法

知道具体宽度情况下 水平居中位置:(总宽度 - 元素宽度) / 2 垂直居中位置:(总高度 - 元素高度) / 2不明确具体宽度情况下 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" co…

VMware虚拟机安装Ubutu

打开vmware按步骤安装 选择安装虚拟机路径 选择下载好的镜像 开启虚拟机 等待 回车确认 空格选择/取消 等待等待好按回车 输入用户名&#xff0c;密码就好了

树形结构-CRUD接口

先看一下效果&#xff1a;整体的效果 新增效果 --默认值是 default 修改效果 - 大致效果如上 --------------------------------------------------------------------------------------------------------------------------------- 下面讲解代码如何实现的 根据你使用…

SpringBoot发送Gmail邮件

1. 登录Gmail Gmail网址 点击右上角“小齿轮”&#xff0c;然后点击"查看所有设置" 点击“转发和 POP/IMAP”&#xff0c;按图中设置&#xff0c;然后点击保存&#xff1a; 2. 启用两步验证(https://myaccount.google.com/security) 登录上述网址&#xff0c;找…

测试FaceRecognitionDotNet报错“Error deserializing object of type int”

FaceRecognitionDotNet宣称是最简单的.net人脸识别模块&#xff0c;其内部使用Dlib、DlibDotNet、OpenCVSharp等模块实现人脸识别&#xff0c;网上有不少介绍文章。实际测试过程中&#xff0c;在调用FaceRecognition.Create函数创建FaceRecognition实例对象时&#xff0c;会报如…