IO
框架
Java IO
的学习是一件非常艰巨的任务。
它的挑战是来自于要覆盖所有的可能性。不仅存在各种
I/O
源端还有想要和他通信的接收端(文件
/
控制台
/
网络链
接),而且还需要以不同的方式与他们进行通信(顺序
/
随机存取
/
缓冲
/
二进制
/
字符
/
行
/
字 等等)这些情况综合起来
就给我们带来了大量的学习任务,大量的类需要学习。
我们要学会所有的这些
java
的
IO
是很难的,因为我们没有构建一个关于
IO
的体系,要构建这个体系又需要深入理解
IO
库的演进过程,所以,我们如果缺乏历史的眼光,很快我们会对什么时候应该使用
IO
中的哪些类,以及什么时候不
该使用它们而困惑。
所以,在开发者的眼中,
IO
很乱,很多类,很多方法,很迷茫。
参考资料:
Java
编程思想 (第
4
版) 第
18
章
Java I/O
系统
IO
简介
数据流是一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。
流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因
此
Java
中的流分为两种:
1)
字节流:
数据流中最小的数据单元是字节
2)
字符流:
数据流中最小的数据单元是字符,
Java
中的字符是
Unicode
编码,一个字符占用两个字节。
Java.io
包中最重要的就是
5
个类和一个接口。
5
个类指的是
File
、
OutputStream
、
InputStream
、
Writer
、
Reader
;
一个接口指的是
Serializable
。掌握了这些就掌握了
Java I/O
的精髓了。
Java I/O
主要包括如下
3
层次:
1.
流式部分
——
最主要的部分。如:
OutputStream
、
InputStream
、
Writer
、
Reader
等
2.
非流式部分
——
如:
File
类、
RandomAccessFile
类和
FileDescriptor
等类
3.
其他
——
文件读取部分的与安全相关的类,如:
SerializablePermission
类,以及与本地操作系统相关的文件系
统的类,如:
FileSystem
类和
Win32FileSystem
类和
WinNTFileSystem
类。
![](https://i-blog.csdnimg.cn/blog_migrate/e6034db1e39e0247c25227cfd19bc496.png)
IO
详细介绍
在
Android
平台,从应用的角度出发,我们最需要关注和研究的就是 字节流(
Stream
)字符流(
Reader/Writer
)
和
File/ RandomAccessFile
。当我们需要的时候再深入研究也未尝不是一件好事。关于字符和字节,例如文本文件,
XML
这些都是用字符流来读取和写入。而如
RAR
,
EXE
文件,图片等非文本,则用字节流来读取和写入。
面对如此复
杂的类关系,有一个点是我们必须要首先掌握的,那就是设计模式中的修饰模式
,学会并理解修饰模式是搞懂流必备
的前提条件哦。
字节流的学习
在具体的学习流之前,我们必须要学的一个设计模式是装饰模式。因为从流的整个发展历史,出现的各种类之间的关
系看,都是沿用了修饰模式,都是一个类的功能可以用来修饰其他类,然后组合成为一个比较复杂的流。比如说:
![](https://i-blog.csdnimg.cn/blog_migrate/f3dba8b7532a965ffecef8442f87f21c.png)
从上面的代码块中大家不难看出这些类的关系:
为了向文件中写入数据,首先需要创建一个
FileOutputStream
,然
后为了提升访问的效率,所以将它发送给具备缓存功能的
BufffferedOutput-Stream,
而为了实现与机器类型无关的
java
基本类型数据的输出,所以,我们将缓存的流传递给了
DataOutputStream
。从上面的关系,我们可以看到,
其根本目的都是为
outputSteam
添加额外的功能。
而这种额外功能的添加就是采用了装饰模式来构建的代码
。因此,
学习流,必须要学好装饰模式。
下面的图是一个关于字节流的图谱,这张图谱比较全面的概况了我们字节流中间的各个类以及他们之间的关系。
字节流的学习过程
为什么要按照一个学习路线来呢?原因是他们的功能决定的。
OutputStream -> FileOutputStream/FilterOutputStream ->DataOutputStream->bufffferedOutputStream
相应的学习
InputStream
方法就好了。
FilterOutputStream
从学习的角度来,我们应该先掌握
FilterOutputStream,
以及
FileOutputStream
,这两个类是基本的类,从继承关系
可以不难发现他们都是对
abstract
类
OutputStream
的拓展,是它的子类。然而,伴随着 对
Stream
流的功能的拓
展,所以就出现了
DataOutputStream
,(将
java
中的基础数据类型写入数据字节输出流中、保存在存储介质中、然
后可以用
DataOutputStream
从存储介质中读取到程序中还原成
java
基础类型)。这里多提一句、
DataOutputStream
、
FilterOutputStream
三个类的关系的这种设计既使用了装饰器模式 避免了类的爆炸式增长。
BufffferedOutputStream
为了提升
Stream
的执行效率,所以出现了
bufffferedOutputStream
。
bufffferedOutputStream
就是将本地添加了一个
缓存的数组。在使用
bufffferedOutputStream
之前每次从磁盘读入数据的时候都是需要访问多少
byte
数据就向磁盘中
读多少个
byte
的数据,而出现
bufffferedOutputSteam
之后,策略就改了,会先读取整个缓存空间相应大小的数据,
这样就是从磁盘读取了一块比较大的数据,然后缓存起来,从而减少了对磁盘的访问的次数以达到提升性能的目的。
另外一方面,我们知道了
outputStream
(输出流)的发展历史后,我们便可以知道如何使用
outpuSteam
了,同样
的方法,我们可以运用到
inputStream
中来,这样对称的解释就出现到了
inputStream
相关的中来了,于是,我们对
整个字节流就有了全方位的理解,所以这样子我们就不会感觉到流的复杂了。这个时候对于其他的一些字节流的使用
(byteArrayOutputStream/PipeOutputStream/ObjectOutputStream)
的学习就自需要在使用的时候看看
API
即可。
字符流的学习
下图则是一个关于字符流的图谱,这张图谱比较全面的概况了我们字符流中间的各个类以及他们之间的关系。
![](https://i-blog.csdnimg.cn/blog_migrate/96f9d9ea567fbf6a87701d8803beacdf.png)
字符流的学习和字节流的学习是一样的,它和字节流有着同样的发展过程,只是,字节流面向的是我们未知或者即使
知道了他们的编码格式也意义不大的文件(
png
,
exe, zip
)的时候是采用字节,而面对一些我们知道文件构造我们
就能够搞懂它的意义的文件(
json
,
xml
)等文件的时候我们还是需要以字符的形式来读取,所以就出现了字符流。
reader
和
Stream
最大的区别我认为是它包含了一个
readline
()接口,这个接口标明了,一行数据的意义,这也是
可以理解的,因为自有字符才具备行的概念,相反字节流中的行也就是一个字节符号。
字符流的学习历程:
Writer- >FilterWriter->BufffferedWriter->OutputStreamWriter->FileWriter->
其他
同时类比着学习
Reader
相关的类。
FilterWriter/FilterReader
字符过滤输出流、与
FilterOutputStream
功能一样、只是简单重写了父类的方法、目的是为所有装饰类提供标准和基
本的方法、要求子类必须实现核心方法、和拥有自己的特色。这里
FilterWriter
没有子类、可能其意义只是提供一个
接口、留着以后的扩展。。。本身是一个抽象类。
BufffferedWriter/BufffferedReader
BufffferedWriter
是
Writer
类的一个子类。他的功能是为传入的底层字符输出流提供缓存功能、同样当使用底层字符输
出流向目的地中写入字符或者字符数组时、每写入一次就要打开一次到目的地的连接、这样频繁的访问不断效率底
下、也有可能会对存储介质造成一定的破坏、比如当我们向磁盘中不断的写入字节时、夸张一点、将一个非常大单位
是
G
的字节数据写入到磁盘的指定文件中的、没写入一个字节就要打开一次到这个磁盘的通道、这个结果无疑是恐怖
的、而当我们使用
BufffferedWriter
将底层字符输出流、比如
FileReader
包装一下之后、我们可以在程序中先将要写入
到文件中的字符写入到
BufffferedWriter
的内置缓存空间中、然后当达到一定数量时、一次性写入
FileReader
流中、此
时、
FileReader
就可以打开一次通道、将这个数据块写入到文件中、这样做虽然不可能达到一次访问就将所有数据写
入磁盘中的效果、但也大大提高了效率和减少了磁盘的访问量!
OutputStreamWriter/InputStreamReader
输入字符转换流、是输入字节流转向输入字符流的桥梁、用于将输入字节流转换成输入字符流、通过指定的或者默认
的编码将从底层读取的字节转换成字符返回到程序中、与
OutputStreamWriter
一样、本质也是使用其内部的一个类
来完成所有工作:
StreamDecoder
、使用默认或者指定的编码将字节转换成字符;
OutputStreamWriter/
InputStreamReader
只是对
StreamDecoder
进行了封装、
isr
内部所有方法核心都是调用
StreamDecoder
来完成的、
InputStreamReader
只是对
StreamDecoder
进行了封装、使得我们可以直接使用读取方法、而不用关心内部实现。
![](https://i-blog.csdnimg.cn/blog_migrate/d0a8ffac1433e678a6e87ad44a7d9917.png)
OutputStreamWriter
中的
StreamEncoder
:
![](https://i-blog.csdnimg.cn/blog_migrate/297863e03756f943fd5171d1b17f0a91.png)
InputStreamReader
中的
StreamDecoder
:
![](https://i-blog.csdnimg.cn/blog_migrate/5162b57265120ec1447aa54919a59f55.png)
在理解这两个流的时候要注意:
java——io
中只有将字节转换成字符的类、没有将字符转换成字节的类、原因很简单
——
字符流的存在本来就像对字节流进行了装饰、加工处理以便更方便的去使用、在使用这两个流的时候要注意:由
于这两个流要频繁的对读取或者写入的字节或者字符进行转码、解码和与底层流的源和目的地进行交互、所以使用的
时候要使用
BufffferedWriter
、
BufffferedReader
进行包装、以达到最高效率、和保护存储介质。
FileReader/FileWriter
FileReader
和
FileWriter
继承于
InputStreamReader/OutputStreamWriter
。
从源码可以发现
FileWriter
文件字符输出流、主要用于将字符写入到指定的打开的文件中、其本质是通过传入的文件
名、文件、或者文件描述符来创建
FileOutputStream
、然后使用
OutputStreamWriter
使用默认编码将
FileOutputStream
转换成
Writer
(这个
Writer
就是
FileWriter
)。如果使用这个类的话、最好使用
BufffferedWriter
包
装一下、高端大气上档次、低调奢华有内涵!
FileReader
文件字符输入流、用于将文件内容以字符形式读取出来、一般用于读取字符形式的文件内容、也可以读
取字节形式、但是因为
FileReader
内部也是通过传入的参数构造
InputStreamReader
、并且只能使用默认编码、所以
我们无法控制编码问题、这样的话就很容易造成乱码。所以读取字节形式的文件还是使用字节流来操作的好、同样在
使用此流的时候用
BufffferedReader
包装一下、就算冲着
BufffferedReader
的
readLine()
方法去的也要使用这个包装
类、不说他还能提高效率、保护存储介质。
字节流与字符流的关系
那么字节输入流和字符输入流之间的关系是怎样的呢?请看下图
![](https://i-blog.csdnimg.cn/blog_migrate/983ad3265b42066f27a28093216a43c6.png)
字节流与字符流的区别
字节流和字符流使用是非常相似的,那么除了操作代码的不同之外,还有哪些不同呢?
字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使
用到缓冲区的字节流在操作文件时,即使不关闭资源(
close
方法),文件也能输出,但是如果字符流不使用
close
方
法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用
flflush
方法强制进行刷新缓冲区,这时才能在
不
close
的情况下输出内容
那开发中究竟用字节流好还是用字符流好呢?
在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片也是按字节完成,而字符是只有
在内存中才会形成的,所以使用字节的操作是最多的。
如果要
java
程序实现一个拷贝功能,应该选用字节流进行操作(可能拷贝的是图片),并且采用边读边写的方式
(节省内存)。
字节流与字符流的转换
虽然
Java
支持字节流和字符流,但有时需要在字节流和字符流两者之间转换。
InputStreamReader
和
OutputStreamWriter
,这两个为类是字节流和字符流之间相互转换的类。
InputSreamReader
用于将一个字节流中的字节解码成字符:
有两个构造方法
:
InputStreamReader(InputStream in);
功能
:用默认字符集创建一个
InputStreamReader
对象
InputStreamReader(InputStream in,String CharsetName);
功能
:接收已指定字符集名的字符串,并用该字符创建对象
OutputStream
用于将写入的字符编码成字节后写入一个字节流。
同样有两个构造方法
:
OutputStreamWriter(OutputStream out);
功能
:用默认字符集创建一个
OutputStreamWriter
对象;
OutputStreamWriter(OutputStream out,String CharSetName);
功能
:接收已指定字符集名的字符串,并用该字符集创建
OutputStreamWrite
对象
为了避免频繁的转换字节流和字符流,对以上两个类进行了封装。
BufffferedWriter
类封装了
OutputStreamWriter
类;
BufffferedReader
类封装了
InputStreamReader
类;
封装格式
:
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(System.out));
BufferedReader in= new BufferedReader(new InputStreamReader(System.in);
利用下面的语句,可以从控制台读取一行字符串:
BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
String line=in.readLine();