程序员潇然 发表于 2022-8-26 16:32:43

JavaIO之再回首恍然(如梦? 大悟?)(二十七)

### 分类回顾

本文是JavaIO告一段落的总结帖
希望对JavaIO做一个基础性的总结(不涉及NIO)
从实现的角度进行简单的介绍

下面的这两个表格,之前出现过

| 数据源形式          | InputStream                  | OutputStream          | Reader          | Writer          |
| ------------------- | ---------------------------- | --------------------- | --------------- | --------------- |
| ByteArray(字节数组) | ByteArrayInputStream         | ByteArrayOutputStream | 无            | 无            |
| File(文件)          | FileInputStream            | FileOutputStream      | FileReader      | FileWriter      |
| Piped(管道)         | PipedInputStream             | PipedOutputStream   | PipedReader   | PipedWriter   |
| Object(对象)      | ObjectInputStream            | ObjectOutputStream    | 无            | 无            |
| String            | ~~StringBufferInputStream~~ | 无                  | StringReader    | StringWriter    |
| CharArray(字符数组) | 无                           | 无                  | CharArrayReader | CharArrayWriter |

| 扩展功能点       | InputStream                | OutputStream         | Reader         | Writer         |
| ---------------- | -------------------------- | -------------------- | ---------------- | -------------- |
| Data(基本类型)   | DataInputStream            | DataOutputStream   | 无               | 无             |
| Buffered(缓冲)   | BufferedInputStream      | BufferedOutputStream | BufferedReader   | BufferedWriter |
| LineNumber(行号) | ~~LineNumberInputStream~~ | 无                   | LineNumberReader | 无             |
| Pushback(回退)   | PushbackInputStream      | 无                   | PushbackReader   | 无             |
| Print(打印)      | 无                         | PrintStream          | 无               | PrintWriter    |

流分为输入输出流两种形式
数据格式又分为字节和字符两种形式

他们组合而来了四大家族
**InputStream      OutputStream      Reader      Writer**

所有的四大家族的流有两种合成扩展方式:

> 按照数据源形式扩展
> 按照装饰功能点扩展

### 数据源形式扩展

换一个维度,从实现的角度,重新介绍下IO

数据源扩展的根本

* 从这种形式的数据中读取数据
写入数据到这种数据形式

我们上面列出来了ByteArrayFile   Piped    ObjectStringCharArray 这几种常用的数据源形式
结合我们上面的概念,我们看一下,实际的实现

#### 字节数组 / 字符数组 /String

#### ByteArray 内存数据

| 类                  | 属性                                                   | 简介                                                                                                                                                      |
| --------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ByteArrayInputStream| 内部有一个byte buf[] 引用,指向实际保存数据的那个字节数组 | ByteArrayInputStream(byte buf[]) ByteArrayInputStream(byte buf[], int offset, int length)构造方法将内部的byte buf[] 引用指向某个 byte buf[]然后就从这里读 |
| ByteArrayOutputStream | 内部有一个byte buf[]缓冲区                               | 构造方法初始化这个缓冲区也就是分配空间,数据的写,就是写到这里面                                                                                              |

#### CharArray 内存数据

| 类            | 属性                                                      | 简介                                                                                                                                    |
| --------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| CharArrayReader | 内部有一个 char buf[]; 引用指向实际保存数据的那个字符数组 | CharArrayReader(char buf[])public CharArrayReader(char buf[], int offset, int length)构造方法将内部的char buf[]; 引用指向某个char buf[] |
| CharArrayWriter | 内部有一个char buf[] 缓冲区                               | 构造方法初始化这个缓冲区,也就是分配空间数据的写,就是写到这里面                                                                        |

### String 内存数据

| 类         | 属性                                                   | 简介                                                                                                                                                                   |
| ------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| StringReader | 内部有一个 String str;引用指向实际的那个提供数据的String | StringReader(String s)构造方法将内部的str   引用指向某个String                                                                                                         |
| StringWriter | 内部有一个StringBuffer buf 用于保存数据                  | public StringWriter()StringWriter(int initialSize)构造方法初始化这个StringBuffer,就是new StringBuffer时可以指定大小 数据的写,就是写到这里面也就是StringBuffer.append |

上面的这三种数据源形式,逻辑非常清晰

**读--->从哪里读?--->你给我一个数据源--->我以IO的操作习惯(InputStream/Reader) 读给你
写--->IO的操作习惯写(OutputStream/Writer) --->写到哪里?--->写到我自己内部的存储里**

有人可能觉得写到你自己内部存储里面干嘛?有毛用?

**ByteArrayOutputStream**

!(data/attachment/forum/202208/26/165611zq6zqmm43qymxt3q.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

**CharArrayWriter**

!(data/attachment/forum/202208/26/165624ri2phhhomxto3eqw.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

**StringWriter**

!(data/attachment/forum/202208/26/165635soxiynz7r818z9xc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

内存数据,如果仅仅是存起来放到他自己肚子里面当然毛用没有
但是,他们都提供了吐出来的功能了
给[字节数组 字符数组String] 提供了一个统一的一致性的读写形式,操作非常方便,不是么

!(data/attachment/forum/202208/26/165653igzldrp1d2d6zlqo.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

真实数据使用引用指向

内部存储是内部的存储区

### 管道

pipe 管道用于直连 然后进行数据的传输
主要用于多线程数据共享

In 输入管道里面有一个存储区
Out 输出管道内有个In的引用

Connect之后,In指向了某个实际的 输入流

然后Out通过引用操作In里面的存储区
In自己的读方法也是操作这个存储区

!(data/attachment/forum/202208/26/165718u5ku87cp37775uwl.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

| col1            | col2                                 | col3                                                                                                                        |
| ----------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| PipedInputStream| 内部有存储区byte buffer[]            | PipedInputStream() PipedInputStream(int pipeSize) 构造方法中给存储区分配空间,可以指定存储区的大小,否则默认值                |
| PipedOutputStream | 内部有一个PipedInputStream sink 引用 | PipedOutputStream() PipedOutputStream(PipedInputStream snk) 无参的后续还需要调用connect 有参数的创建对象时就进行connect连接 |
| PipedReader       | 内部有存储区 char buffer[];          | PipedReader() PipedReader(int pipeSize) 构造方法中给存储区分配空间,可以指定大小,否则默认值                                  |
| PipedWriter       | 内部有一个PipedReader sink;引用    | PipedWriter() PipedWriter(PipedReader snk) 无参的后续还需要调用connect有参数的创建对象时进行connect连接                   |

所以一旦理解了,JavaIO管道的模型,管道就实在是太简单了

!(data/attachment/forum/202208/26/170233k6udrdflhulyhu6d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

只需要记住:
输入In里面 有一个存储缓冲区, 输出有一个引用指向了In
connect将他们连接起来,他们共同操作一个池子
输出往里面写,输入从里面读
管子的方向只能是 :    输出 -----> 输入

### 文件

文件相关的,都是实实在在的要通过操作系统了
所以也就必然需要使用本地方法

在Java中一个文件使用File来描述,File是抽象路径名 可以表示文件也可以表示目录
File可以通过String路径名构造
另外还有文件描述符可以表示指代文件

#### File 磁盘文件

| 类               | 属性                                                                                                                                                             |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| FileInputStream| 操作文件 构造方法可以使用:File /String的路径名 /文件描述符   来创建 实实在在的一个InputStream的实现类,最终通过本地方法来进行数据读取                           |
| FileOutputStream | 操作文件构造方法可以使用: File/ String的路径名 /文件描述符   来创建 另外他还有是否追加的概念 实实在在的一个OutputStream的实现类,最终通过本地方法来进行数据写入 |

底层文件本身是二进制存储的

如果你想要通过字符去操作文件,必然要经过 编码和解码的过程

!(data/attachment/forum/202208/26/170631pmvnx77ommscxvxa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

JavaIO提供了InputStreamReader    OutputStreamWriter两个转换流来实现编码和解码
想要彻底理解还是需要理解适配器模式
这两个流都是字符和字节的转换,只不过是方向不同
从字节到字符,这就是解码;   从字符到字节,这就是编码

InputStreamReader   字节流到字符流的桥梁, 也就是解码   从上图看,二进制才是码,从码到字符
OutputStreamWriter 字符流到字节流的桥梁, 也就是编码   从上图看,二进制才是码,从字符到码

根据上面的说法,FileReader 和 FileWriter必然要是一种转换流

| 类         | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| FileReader | 文件本身都是二进制序列 字节形式<br />所以必然有字节InputStream到字符Reader的转换 <br />而InputStreamReader 恰恰是字节通向字符的桥梁 <br />所以 FileReader继承了InputStreamReader是一种特殊的InputStreamReader<br /><br /> InputStreamReader 将InputStream适配成Reader   所以需要InputStream作为参数进行构造<br />文件的字节输入流--FileInputStream可以使用:File /String的路径名 /文件描述符来创建所以FileReader的构造方法接受这几种参数, 构造一个FileInputStream <br />然后调用InputStreamReader 的构造方法<br /><br /> InputStreamReader字节到字符 解码 涉及到码表InputStreamReader构造方法部分需要指定编码                                                                                                                                          |
| FileWriter | 文件本身都是二进制序列 字节形式<br />所以想要写入数据必然有字符Writer到字节OutputStream的转换<br /> 而OutputStreamWriter 恰恰是字符通向字节的桥梁 <br />所以 FileWriter继承了OutputStreamWriter是一种特殊的OutputStreamWriter <br /><br />OutputStreamWriter 将OutputStream适配成Writer   <br />所以需要OutputStream作为参数进行构造 <br />文件的字节输出流 --FileOutputStream可以使用:File /String的路径名 /文件描述符   来创建 <br />所以FileWriter的构造方法接受这几种参数 然后构造一个FileOutputStream,调用OutputStreamWriter 的构造方法 <br /><br />另外FileOutputStream 还有追加的概念 <br />所以FileWriter 的构造方法里面也有append 追加这一项<br /><br /> OutputStreamWriter字符到字节 编码 涉及到编码 OutputStreamWriter构造方法部分需要指定编码 |

关于FileReader 和FileWriter说了那么多

其实只有两句话,就是字节流与字符流之间进行转换

```java
Reader reader = new InputStreamReader( new FileInputStream(.......));
Writer writer = new OutputStreamWriter( new FileOutputStream(.......));
```

#### **Object**

对于文件中的字符与字节的转换,可以通过某些编码表,查表法确定编码的值,进而完成字符与字节之间的相互转换
那么,对于一个对象呢?
Object是内存中的数据,他并不是一串字符形式
有一个概念叫做         序列化与反序列化
其实就了类似字符的编码与解码

!(data/attachment/forum/202208/26/171539t3rxwc4wgx34rpc4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

从这个图应该能感知到ObjectInputStream和ObjectOutputStream    与 字符流的逻辑类似么
字符与字节转换 是一种编码解码的过程
对象序列化与反序列化 不也是一种编码解码的过程吗 ,只不过这个编码解码不是单纯的查询码表这么简单
字符流与字节流的转换,可以通过转换流进行处理
Object序列化与反序列化是
ObjectInputStreamObjectOutputStream他们分别实现ObjectInput 和 ObjectOutput来提供的
所以从这个角度讲的话,可以把Object理解成为一种很特殊的"字符"
他们两个就像InputStreamReader和 OutputStreamWriter似的,用来转换

### 功能的装饰扩展

既然是功能的装饰扩展,我们之前已经说过很多次,都是装饰器模式
也就是说了很多遍的

> 是你还有你,一切拜托你,中间增加点功能

这个你,就是需要被装饰的抽象角色Component
就是这四大家族      InputStream      OutputStream      Reader      Writer
给读和写装饰增加新的功能,也就是最根本的读和写方法,将都是使用ConcreteComponent的
在基本的读和写方法之上,提供了新的功能


#### Data

| 类               | 结构                                                                                     | 说明                                                                                                             |
| ---------------- | ---------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- |
| DataOutputStream | 继承自FilterOutputStream<br />得到一个InputStream引用in<br />构造方法需要InputStream   | 通过in.read系列方法读取,   然后将读取的数据   <br /> 组装成基本数据类型<br />进而提供读取基本数据类型的能力 |
| DataInputStream| 继承自FilterInputStream<br />得到一个OutputStream 引用out <br />构造方法需要OutputStream | 将基本类型数据进行转化处理,    <br />然后调用out.write系列方法将数据写入 <br />进而提供写入基本数据类型的能力 |

#### Buffered

缓冲的概念都是内部有一个缓冲区
缓冲输入是通过底层的流往自己的缓冲区写入数据, 应用程序从缓冲输入的缓冲区中读取,提高了read速度
缓冲输出是把数据写入到自己的缓冲区中,后续再把数据通过底层的流一并写入,从而提高了write的速度
因为读写都是从缓冲区中进行的了

| 类                   | 说明                                                                                                                  |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| BufferedInputStream| 继承自FilterInputStream <br />得到一个InputStream引用in <br />构造方法需要InputStream <br />内部有一个缓冲区byte buf[]|
| BufferedOutputStream | 继承自FilterOutputStream <br />得到一个OutputStream 引用out <br />构造方法需要OutputStream <br />内部有一个缓冲区buf[]; |
| BufferedReader       | 内部有Reader 引用 in <br /> 构造方法需要一个Reader <br />内部有一个缓冲区char cb[];                                     |
| BufferedWriter       | 内部有一个Writer 引用out<br />构造方法需要一个Writer<br />内部有一个缓冲区char cb[];                              |

`本文作者:程序员潇然 疯狂的字节X https://crazybytex.com/`


#### LineNumberReader

内部使用了一个lineNumber = 0;用来记录行号
这个行号可以使用方法设置和获取
getLineNumbersetLineNumber但是他不改变流的位置


#### PushBack

装饰器模式 方法依赖于被装饰的实体 ConcreteComponent
只是内部有一个缓冲区,可以存放被回退掉的字符
所有的读取方法在进行读取的时候,都会查看缓冲区的数据

| 类                  | 说明                                                                                                                  |
| ------------------- | --------------------------------------------------------------------------------------------------------------------- |
| PushbackInputStream | 继承自FilterInputStream <br />得到一个InputStream 引用in <br />构造方法需要 InputStream<br />内部有缓冲区byte[] buf |
| FilterReader      | 继承自FilterReader <br />得到一个Reader引用 in<br /> 构造方法需要一个Reader<br />内部有缓冲区char[] buf         |

#### print

提供了多种形式的打印,根本只是在真的写入数据前,将数据参数进行一些处理
根本的写操作 依赖被装饰的节点流提供
在数据写入之前进行必要的数据处理

| 类          | 说明                                                                                       |
| ----------- | -------------------------------------------------------------------------------------------- |
| PrintStream | 继承自 FilterOutputStream<br />得到一个OutputStream 引用 out<br />构造需要一个OutputStream |
| PrintWriter | 内部有一个out   构造方法需要一个Writer                                                   |



扩展的功能通过装饰器模式,他们的行为都是类似的,那就是:

1. 最基本的读写依赖被装饰的具体的节点流
2. 然后进行了功能的增强



### 总结

又从实现的角度把常用的一些流进行了介绍
你会发现看起来那么多,实际上并没有多少
四大家族,以及几种数据源形式,以及几个扩展功能点
只要找准了思路,理清楚了逻辑,就不难理解了

不知道到底是恍然大悟?还是?恍然如梦?

!(data/attachment/forum/202206/16/141330jha7st9soow8772i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "common_log.png")
`转载务必注明出处:程序员潇然,疯狂的字节X,https://crazybytex.com/thread-163-1-1.html `
页: [1]
查看完整版本: JavaIO之再回首恍然(如梦? 大悟?)(二十七)