流分类回顾
本文是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[]缓冲区 |
构造方法初始化这个缓冲区,也就是分配空间 数据的写,就是写到这里面 |
|
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 |
|
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链接 |
|
因此一旦理解了,JavaIO管道的模型,管道就实在是太简单了
|
只须要记住: 输入In里面 有一个存储缓冲区, 输出有一个引用指向了In connect将他们链接起来,他们共同操做一个池子 输出往里面写,输入从里面读 管子的方向只能是 : 输出 -----> 输入
|
文件
文件相关的,都是实实在在的要经过操做系统了 因此也就必然须要使用本地方法
在Java中一个文件使用File来描述,File是抽象路径名 能够表示文件 也能够表示目录 File能够经过String路径名构造 另外还有文件描述符能够表示指代文件 |
File 磁盘数据 |
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 和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系列方法将数据写入 进而提供写入基本数据类型的能力 |
|
缓冲的概念都是内部有一个缓冲区
缓冲输入 是经过底层的流往本身的缓冲区写入数据, 应用程序从缓冲输入的缓冲区中读取,提升了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[]; |
|
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. 而后进行了功能的加强
总结
说到这个地方,咱们又从实现的角度把经常使用的一些流进行了介绍
你会发现看起来那么多,实际上并无多少
四你们族,以及几种数据源形式,以及几个扩展功能点
只要找准了思路,理清楚了逻辑,就不难理解了
不知道究竟是恍然大悟?仍是?恍然如梦 ?