[二十六]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

数据源扩展的根本

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

我们上面列出来了ByteArray  File   Piped    Object  String  CharArray 这几种常用的数据源形式

结合我们上面的概念,我们看一下,实际的实现

字节数组 / 字符数组 /String

ByteArray内存数据

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

ByteArrayInputStream

内部有一个byte buf[] 引用指向实际保存数据的那个字节数组

ByteArrayInputStream(byte buf[])ByteArrayInputStream(byte buf[], int offset, int length)构造方法将内部的byte buf[] 引用指向某个 byte buf[]然后就从这里读

ByteArrayOutputStream

内部有一个byte buf[]缓冲区

构造方法初始化这个缓冲区,也就是分配空间数据的写,就是写到这里面

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[] 缓冲区 构造方法初始化这个缓冲区,也就是分配空间 数据的写,就是写到这里面

CharArrayReader

内部有一个 char buf[]; 引用指向实际保存数据的那个字符数组

CharArrayReader(char buf[])public CharArrayReader(char buf[], int offset, int length)构造方法将内部的char buf[]; 引用指向某个char buf[]然后就从这里读

CharArrayWriter

内部有一个char buf[] 缓冲区

构造方法初始化这个缓冲区,也就是分配空间 数据的写,就是写到这里面

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

StringReader

内部有一个 String str;引用指向实际的那个提供数据的String

StringReader(String s)构造方法将内部的str   引用指向某个String然后就从这里读

StringWriter

内部有一个StringBuffer buf 用于保存数据

public StringWriter() StringWriter(int initialSize) 构造方法初始化这个StringBuffer,就是new StringBuffer时可以指定大小  数据的写,就是写到这里面  也就是StringBuffer.append

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

CharArrayWriter

StringWriter

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

真实数据使用引用指向

内部存储是内部的存储区


管道

pipe 管道用于直连 然后进行数据的传输主要用于多线程数据共享In 输入管道里面有一个存储区Out 输出管道内有个In的引用Connect之后,In指向了某个实际的 输入流然后Out通过引用操作In里面的存储区In自己的读方法也是操作这个存储区

Pipe

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连接

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连接

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管道的模型,管道就实在是太简单了

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


文件

文件相关的,都是实实在在的要通过操作系统了所以也就必然需要使用本地方法在Java中一个文件使用File来描述,File是抽象路径名 可以表示文件  也可以表示目录 File可以通过String路径名构造另外还有文件描述符可以表示指代文件

File磁盘数据

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

FileInputStream

操作文件构造方法可以使用:  File /String的路径名 /文件描述符   来创建实实在在的一个InputStream的实现类,最终通过本地方法来进行数据读取

FileOutputStream

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

FileInputStream

操作文件构造方法可以使用:  File /String的路径名 /文件描述符   来创建实实在在的一个InputStream的实现类,最终通过本地方法来进行数据读取

FileOutputStream

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

底层文件本身是二进制存储的,如果你想要通过字符去操作文件,必然要经过 编码和解码的过程

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

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

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

File 磁盘数据

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

FileReader

文件本身都是二进制序列 字节形式,所以必然有字节InputStream到字符Reader的转换 而InputStreamReader 恰恰是字节通向字符的桥梁 所以 FileReader继承了InputStreamReader  是一种特殊的InputStreamReader InputStreamReader 将InputStream适配成Reader   所以需要InputStream作为参数进行构造 文件的字节输入流--FileInputStream可以使用:  File /String的路径名 /文件描述符  来创建 所以FileReader的构造方法接受这几种参数, 构造一个FileInputStream 然后调用InputStreamReader 的构造方法 InputStreamReader字节到字符 解码 涉及到码表  InputStreamReader构造方法部分需要指定编码

FileWriter

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

FileReader

文件本身都是二进制序列 字节形式,所以必然有字节InputStream到字符Reader的转换 而InputStreamReader 恰恰是字节通向字符的桥梁 所以 FileReader继承了InputStreamReader  是一种特殊的InputStreamReader InputStreamReader 将InputStream适配成Reader   所以需要InputStream作为参数进行构造 文件的字节输入流--FileInputStream可以使用:  File /String的路径名 /文件描述符  来创建 所以FileReader的构造方法接受这几种参数, 构造一个FileInputStream 然后调用InputStreamReader 的构造方法 InputStreamReader字节到字符 解码 涉及到码表  InputStreamReader构造方法部分需要指定编码

FileWriter

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

关于FileReader 和FileWriter说了那么多

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

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


Object

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

从这个图应该能感知到ObjectInputStream和ObjectOutputStream    与 字符流的逻辑类似么字符与字节转换 是一种  编码解码的过程对象序列化与反序列化 不也是一种编码解码的过程吗 ,只不过这个编码解码不是单纯的查询码表这么简单

字符流与字节流的转换,可以通过转换流进行处理Object序列化与反序列化是ObjectInputStream  ObjectOutputStream  他们分别实现ObjectInput 和 ObjectOutput来提供的所以从这个角度讲的话,可以把Object理解成为一种很特殊的"字符"他们两个就像InputStreamReader  和 OutputStreamWriter似的,用来转换

功能的装饰扩展

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

也就是说了很多遍的

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

这个,就是需要被装饰的抽象角色Component   

就是这四大家族      InputStream        OutputStream        Reader        Writer

给读和写装饰增加新的功能,也就是最根本的读和写方法,将都是使用ConcreteComponent

在基本的读和写方法之上,提供了新的功能

Data

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

DataInputStream

继承自FilterInputStream得到一个InputStream引用in构造方法需要InputStream

通过in.read系列方法读取,   然后将读取的数据    组装成基本数据类型进而提供读取基本数据类型的能力

DataOutputStream

继承自FilterOutputStream得到一个OutputStream 引用out构造方法需要OutputStream

将基本类型数据进行转化处理,    然后调用out.write系列方法将数据写入进而提供写入基本数据类型的能力

DataInputStream

继承自FilterInputStream得到一个InputStream引用in构造方法需要InputStream

通过in.read系列方法读取,   然后将读取的数据    组装成基本数据类型进而提供读取基本数据类型的能力

DataOutputStream

继承自FilterOutputStream得到一个OutputStream 引用out构造方法需要OutputStream

将基本类型数据进行转化处理,    然后调用out.write系列方法将数据写入进而提供写入基本数据类型的能力

缓冲的概念都是内部有一个缓冲区

缓冲输入  是通过底层的流往自己的缓冲区写入数据, 应用程序从缓冲输入的缓冲区中读取,提高了read速度

缓冲输出  是把数据写入到自己的缓冲区中,后续再把数据通过底层的流一并写入,从而提高了write的速度

因为读写都是从缓冲区中进行的了

Buffered

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

BufferedInputStream

继承自FilterInputStream 得到一个InputStream引用in 构造方法需要InputStream 内部有一个缓冲区byte buf[]

BufferedOutputStream

继承自FilterOutputStream 得到一个OutputStream 引用out 构造方法需要OutputStream 内部有一个缓冲区buf[];

BufferedReader

内部有Reader 引用 in构造方法需要一个Reader内部有一个缓冲区char cb[];

BufferedWriter

内部有一个Writer 引用  out构造方法需要一个Writer内部有一个缓冲区char cb[];

BufferedInputStream

继承自FilterInputStream 得到一个InputStream引用in 构造方法需要InputStream 内部有一个缓冲区byte buf[]

BufferedOutputStream

继承自FilterOutputStream 得到一个OutputStream 引用out 构造方法需要OutputStream 内部有一个缓冲区buf[];

BufferedReader

内部有Reader 引用 in构造方法需要一个Reader内部有一个缓冲区char cb[];

BufferedWriter

内部有一个Writer 引用  out构造方法需要一个Writer内部有一个缓冲区char cb[];

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

PushBack

装饰器模式 方法依赖于被装饰的实体 ConcreteComponent

只是内部有一个缓冲区,可以存放被回退掉的字符

所有的读取方法在进行读取的时候,都会查看缓冲区的数据

PushbackInputStream

继承自FilterInputStream 得到一个InputStream 引用in构造方法需要 InputStream内部有缓冲区byte[] buf

FilterReader

继承自FilterReader 得到一个Reader引用 in构造方法需要一个Reader内部有缓冲区char[] buf

Print

提供了多种形式的打印,根本只是在真的写入数据前,将数据参数进行一些处理

根本的写操作 依赖被装饰的节点流提供

在数据写入之前进行必要的数据处理

PrintStream

继承自 FilterOutputStream得到一个OutputStream 引用 out构造需要一个OutputStream

PrintWriter

内部有一个out构造方法需要一个Writer

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

1. 最基本的读写依赖被装饰的具体的节点流

2. 然后进行了功能的增强

总结

说到这个地方,我们又从实现的角度把常用的一些流进行了介绍

你会发现看起来那么多,实际上并没有多少

四大家族,以及几种数据源形式,以及几个扩展功能点

只要找准了思路,理清楚了逻辑,就不难理解了

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏noteless

[二十四]JavaIO之PrintWriter

他与PrintStream的逻辑上功能目的是相同的--他们都想做同一件事情--更便捷的格式化打印输出

4012
来自专栏向治洪

Kotlin 是如何避免空指针问题的

在谈Kotlin的优势的时候,大家都会想到空指针安全这一点,那么Kotlin又是如何避免这些问题的呢?下面从Kotlin的一些语法规则上给出介绍。 可空类型 默...

2847
来自专栏个人随笔

Java 持久化操作之 --io流与序列化

1)File类操作文件的属性 1.File类的常用方法 ? 1. 文件的绝对完整路径:getAbsolutePath() 文件名:getName() 文件相对路...

2959
来自专栏吴伟祥

Java中Interface的方法和常量的默认访问修饰符

Java中Interface常量的默认访问修饰符为:public static final 

5423
来自专栏携程技术中心

干货 | Kotlin超棒的语言特性

1714
来自专栏码神联盟

面试题 | 《Java面试题集》 -- 第二套

start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程,进而调用run()方法来执行任务,而单独的调用run()就跟调用普通方法是一样...

1112
来自专栏Android Note

介绍Kotlin第二部分(翻译篇)

1474
来自专栏C语言及其他语言

【优秀题解】问题 1113: C语言考试练习题_保留字母

斐波纳契数列 1,1,2,3,5,8,13,21,34,55,89……这个数列则称为“斐波纳契数列”,其中每个数字都是“斐波纳契数”。

1073
来自专栏java闲聊

JDK8-Lambda表达式集合操作

public static List<Dish> toLists(){ return Arrays.asList( new...

993
来自专栏云霄雨霁

词法分析程序

1810

扫码关注云+社区

领取腾讯云代金券