目录
Java编程思想(一)第1~4章:概述
Java编程思想(二)第5章:初始化和清理
Java编程思想(三)第6章:访问权限
Java编程思想(四)第7章:复用类
Java编程思想(五)第8章:多态
Java编程思想(六)第9章:接口
Java编程思想(七)第10章:内部类
Java编程思想(八)第11章:持有对象
Java编程思想(九)第12章:异常
Java编程思想(十)第13章:字符串
Java编程思想(十一)第14章:类型信息
Java编程思想(十二)第15章:泛型
Java编程思想(十三)第16章:数组
Java编程思想(十四)第17章:深入研究容器
Java编程思想(十五)第18章:Java I/O系统
Java编程思想(十六)第19章:枚举
Java编程思想(十七)第20章:注解
Java编程思想(十八)第21章:并发
第十八章、Java的I/O系统
目录
1. File类
2. 输入和输出
3. 添加属性和有用的接口
4. Reader和Writer
5. 自我独立的类:RandomAccessFile
6. I/O流的典型使用方式
7. 文件读写的使用工具
8. 标准IO
9.进程控制
10. 新I/O
11. 压缩
12 对象序列化(Android ->Parcelable)
13 XML(android *.xml 即使用此机制)
15 总结:
对程序语言的设计者来说,创建一个好的I/O系统是一个艰难的任务:
File(文件)既能代表一个特定文件名称,又能代表一个目录下的一组文件的名称。如果是文件集,可以对此集合调用list()方法,返回一个字符数组。
1.1 目录列表
查看一个文件目录的方法(使用File类):
1.2 目录实用工具
在文件集上执行操作:
1.3 目录的检查:
文件的操作
Java类库中的I/O类分成输入和输出两部分:
2.1 InputStream类型
InputStream的作用是用来表示从不同数据源产生输出的类,这些数据源(均为InputStream的子类)包括:
2.2 OutputStream类型
该类别的类决定了输出所要去往的目标,包含:
装饰者(GoF23之一):动态的将功能附加到对象上,在对象扩展方面,它比继承更加有弹性。
3.1 通过FilterInputStream从InputStream读取数据
FilterInputStream类能够完成完全不同的事情,其中,DateInputStream允许读取不同的基本类型数据以及String对象。
其他FilterInputStream类则在内部修改InputStream的行为方式:是否缓冲,是否保留它所读过的行(允许查询行数或设置行数),以及是否把单一字符推回输入流。
3.2 通过FilterOutputStream向OutputStream导入
InputStream和OutputStream在以面向字节形式的IO中可以提供极有价值的功能,Reader和Writer(Java 1.1对基础IO流类库进行了重大修改,可能会以为是用来替换InputStream和OutputStream的)则提供兼容Unicode和面向字符的IO功能。
适配器(GoF23之一):将一个类的接口转换成客户端希望的另一个接口。
设计Reader和Writer继承层次结构只要是为了国际化。老的IO流继承层次结构仅支持8位字节流,并且不能很好地处理16位的Unicode字符。所以Reader和Writer继承层次结构就是为了在所有IO操作中都支持Unicode。
4.1 数据的来源和去处
4.2 更改流的行为
对于InputStream和OutputStream来说,有装饰器子类来修改流以满足需要。Reader和Writer的类继承层次结构继续沿用相同的思想——但不完全相同。无论何时使用readLine(),都不应该使用DataInputStream,而应该使用BufferedReader。
为了更容易地过渡到使用PrintWriter,它提供了一个既接受Writer对象又能接受任何OutputStream对象的构造器。PrintWriter的格式化接口实际上与PrintStream相同。
RandomAccessFile适用于由大小已知的记录组成的文件,所以可以使用seek()将记录从一处转移到另一处,然后读取或者修改记录。文件中记录的大小不一定都相同,只要能够确定那些记录有多大以及它们在文件中的位置即可。
RandomAccessFile实现了DataInput和DataOutput接口,它是一个完全独立的类,从头开始编写其所有的方法(大多数都是本地的)。这么做是因为RandomccessFile拥有和别的I/O类型本质不同的行为,因为可以在一个文件内向前和向后移动。在任何情况下,它都是自我独立的,直接从Object派生而来。
方法getFilePointer()用于查找当前所处的文件位置,seek()用于在文件内移至新的位置,length()用于判断文件的最大尺寸。另外,其构造器还需要第二个参数(和C中的fopen()相同)用来指示我们只是“随机读”®还是“既读又写”(rw)。
6 I/O流的典型使用方式(Typical uses of I/O streams)
尽管可以通过不同的方式组合I/O流类,但我们可能也就只用到其中的几种组合。下面的例子可以作为
6.1 缓冲输入文件
使用以String或File对象作为文件名的FileInputReader。为了提高速度,对文件进行缓冲,将所产生的引用传给一个BufferedReader构造器。
BufferedReader in = new BufferedReader(new FileReader(filename));
6.2 从内存输入
从BufferedInputFile.read()读入的String结果被用来创建一个StringReader。然后调用read()每次读取一个字符,并发送到控制台。
StringReader in = new StringReader(BufferedInputFile.read("MemoryInput.java"));
6.3 格式化的内存输入
要读取格式化数据,可以使用DataInputStream,它是面向字节的IO类,因此必须用InputStream而不是Reader。
DataInputStream in = new DataInputStream(
new ByteArrayInputStream(BufferedInputFile.read("FormattedMemoryInput.java").getBytes()));
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(“TestEOF.java”)));
6.4 基本文件输出
FileWriter对象可以向文件写入数据。通常会用BufferedWriter将其包装起来用以缓冲输出。
BufferedReader in = new BufferedReader(new StringReader(
BufferedInputFile.read("BasicFileOutput.java")));
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
6.5 存储和恢复数据
PrintWriter可以对数据进行格式化,以便阅读。但是为了输出可供另一个流恢复的数据,需要用DataOutputStream写入数据:
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(“Data.txt”)));
DataInputStream恢复数据:
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(“Data.txt”)));
6.6 读写随机访问文件
RandomAccessFile rf = new RandomAccessFile(file, “rw”);
读取文件:
BufferedReader in = new BufferedReader(new FileReader((new File(fileName)).getAbsoluteFile()));
写入文件:
PrintWriter out = new PrintWriter((new File(fileName)).getAbsoluteFile());
标准IO源自于Unix的“程序所使用的单一信息流”这一概念。程序的所有输入都可以来自于标准输入,所有输出都可以发送到标准输出。
8.1 从标准输入中读取
通常会用readLine()一次一行读取输入,将System.in包装城BufferedReader来使用,这要求必须用InputStreamReader把Sytem.in转换成Reader。System,in通常应该对它进行缓冲。
8.2 将System.out转换成PrintWriter
PrintWriter out = new PrintWriter(System.out, true);
8.3 标准IO重定向
Java的System类提供了静态方法嗲用,以允许对标准输入输出和错误IO流进行重定向:
Java内部执行其他操作系统的程序,并且控制这些程序输入输出,Java类库提供了执行这些操作的类:ProcessBuilder对象
JDK 1.4的java.nio.*包中引入了新的IO类库,其目的在于提高速度。实际上,旧的IO包已经使用nio重新实现过,以便充分利用这种速度提高。
速度提高源自于所使用的结构更接近于操作系统执行IO的方式:通道和缓冲器
唯一直接与通道交互的缓冲器是ByteBuffer,可以存储未加工字节的缓冲器。java.nio.ByteBuffer是相当基础的类:通过稿子分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用于以原始的字节形式或基本数据类型输出和读取数据。但是,没办法输出或读取对象,即使是字符串对象也不行。这种处理虽然很低级,但却正好,因为这是大多数草走系统中更有效的映射方式。
旧IO类库有三个类被修改了,用以产生FileChannel。这三个被修改类是FileInputStream、FileOutputStream以及用于既读又写的RandomAccessFile。这些都是字节操作流,与底层nio性质一致。Reader和Writer这些字符模式类不能用于产生通道;但是java.nio.channels.Channels类提供了使用方法,用于在通道中产生Reader和Writer。
getChannel()将会产生一个FileChannel。通道是一种相当基础的:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。
使用warp()方法将已存在的字节数组包装到ByteBuffer中。
data.txt文件用RandomAccessFile被再次打开。注意可以在文件内随处移动FileChanel;这里,把它移动到最后,以便附加其他写操作。
对于只读访问,必须显式地使用静态的allocate()方法来分配ByteBuffer。nio的目的就是快速移动大量数据,因此ByteBuffer的大小显得尤为重要——实际上,使用1K可能比通常使用的小一点(必须通过实际运行应用程序来找到最佳尺寸)。甚至叨叨更高速度,使用allocateDirect(),以产生一个与操作系统有更高耦合性的直接缓冲器(但分配的开支会更大)。
一旦调用read()来告知FileChannel向ByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备。如果打算使用缓冲器执行进一步read()操作,也必须得使用clear()来为每个read()做好准备。
10.1 转换数据
在GetChannel.java中,必须每次只读取一个字节的数据,然后将每个byte类型强制转换成char类型。而java.nio.CharBuffer有一个toString方法:返回一个包含缓冲器中所有字符的字符串。
10.2 获取基本类型
尽管ByteBuffer只能保存字节类型数据,但是它可以从其所容纳的字节中产生出各种不同的基本类型值的方法
bb.asCharBuffer();
bb.asShortBuffer();
bb.asIntBuffer();
bb.asLongBuffer();
bb.asFloatBuffer();
bb.asDoubleBuffer();
10.3 视图缓冲器
视图缓冲器(view buffer)可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。对视图的任何修改都会映射成为对ByteBuffer中数据的修改。
先用重载后的put()方法存储一个整数数组。接着get()和put()方法调用直接访问底层ByteBuffer中的某个整数位置。注意,这些通过直接与ByteBuffer对话访问绝对位置的方式也同样适用于基本类型。
一旦底层的ByteBuffer通过视图缓冲器填满了整数或其他基本类型时,就可以直接被写到通道中。正像从通道中读取那样容易,然后使用视图缓冲器可以把任何数据都转化为某一特定的基本类型。
10.4 用缓冲器操纵数据
此图阐明了nio类之间的关系,便于理解怎么移动和转换数据。如果想把一个字节数组写到文件中去,那么就应该使用ByteBuffer.wrap()方法把字节数组包装起来,然后用getChannel()方法在FileOutputStream上打开一个通道,接着将来自于ByteBuffer的数据写到FileChannel。
注意:BytBuffer是将数据移进移出通道的唯一方式,并且只能创建一个独立的基本类型缓冲器,或者使用as方法从ByteBuffer中获得。也就是说,不能把基本类型的缓冲器转换成ByteBuffer。
10.5 缓冲器的细节
Buffer有数据和可以高效地访问及操作这些数据的四个索引组成,mark(标记)、position(位置)、limit(界限)和capacity(容量)。
10.6 内存映射文件(MemoryMappedFile)
内存映射文件允许创建和修改因为太大而不能放入内存的文件。可以假定整个文件都放在内存中,而且可以完全把它当作非常大的数组访问
MappedByteBuffer out = new RandomAccessFile("test.dat", "rw").getChannel()
.map(FileChannel.MapMode.READ_WRITE, 0, length);
10.6.1 性能
nio实现后性能有所提高,但是映射文件访问往往可以更加显著地加快速度。
10.7 文件加锁
JDK 1.4引入了文件加锁机制,允许同步访问某个做为共享资源的文件。文件锁对其他的操作系统进程是可见的,因为Java的文件加锁直接映射到本地操作系统的加锁工具。
FileOutputStream fos= new FileOutputStream("file.txt");
FileLock fl = fos.getChannel().tryLock();
Java IO类库中的类支持读写压缩格式的数据流。 Java IO类库中的类支持读写压缩格式的数据流。
11.1 用GZIP进行简单压缩
如果相对单一数据流进行压缩,GZIP接口是比较合适的选择:
压缩:BufferedOutputStream <- GZIPOutputStream <- FileOutputStream
解压:BufferedReader <- InputStreamReader <- GZIPInputStream <- FileInputStream
11.2 用Zip进行多文件保存
支持Zip格式的Java库更加全面,它显示了用Checksum类来计算和校验文件的校验和的方法。一共有两种Checksum类型:Adler32(快)和CRC32(慢,准确)。
压缩:BufferedOutputStream <- ZIPOutputStream <- CheckedOutputStream <- FileOutputStream
解压: BufferedReader <- InputStreamReader <- ZIPInputStream <- CheckedInputStream <- FileInputStream
11.3 Java档案文件(JAR)
JAR(Java ARchive,Java档案文件)文件格式:
将一组文件压缩到单个压缩文件中。同Java中任何其他东西一样,JAR也是跨平台的。由于采用压缩技术,可以使传输时间更短,只需向服务器发送一次请求即可。
对象序列化的相关概念:
对象序列化的概念加入到语言中是为了支持两种主要特性。
对象序列化的实现:
底层实现步骤:
12.1 寻找类
将一个对象从它的序列化状态中恢复出来,哪些工作是必须的?
12.2 序列化的控制
12.1. 1 Serializable接口
12.1. 2 Exterbalizable(implements Serializable)接口
12.1. 3 Serializable对象与Externalizable对象的区别:
12.1. 4 transient(瞬时)关键字
12.1. 5 Externalizable的替代方法
Externalizable的替代方案1:
private void writeObject(ObjectOutputStream stream)throws IOException;
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException;
Externalizable的替代方案2:
12.1. 6 深度拷贝&序列化