二进制I/O
17.1 引言
文件分类:
可以使用文本编辑器进行处理(读取、创建或者修改)的文件被称为文本文件。所有
所有其他的文件都被称为二进制文件。
不能使用文本编辑器来读取二进制文件——它们是为让程序来读取而设计的。就像,Java源程序存储在文本文件中,可以使用文本编辑器读取,而Java类是二进制文件,由Java虚拟机读取。
可以简单的认为: 文本文件是由字符序列构成的,而二进制是由位bit序列构成的。
二进制文件的优势在于它的处理效率比文本文件高。
17.2 在Java中如何处理文本I/O
要点提示: 使用Scanner类读取文本数据,使用PrintWriter类写文本数据。
File类: 封装了文件或路径属性,但是不包含从/向文件读/写数据的方法。
I/O类: 包含从/向文件读/写数据的方法。
输出对象: 输出流
输入对象: 输入流
17.3 文本I/O与二进制I/O
要点提示 :二进制I/O不涉及编码和解码,所以更高效
计算机并不区分二进制文件和文本文件。所有的文件都是以二进制形式来存储的,从本质上来说,所有的文件都是二进制的。
文本I/O能提供一层抽象,用于字符串层次的编码和解码。对于文本I/O,编码和解码是自动进行的。
对于文本编辑器或文本输出程序创建的文件,应该使用文本输入来读取,对于Java二进制输出程序创建的文件,应该使用二进制输入来读取。
二进制I/O不需要编码和解码,所以它的效率更高。同时,由于二进制文件与主机的编码方案无关,因此,它是可移植的。这也是Java的类文件存储为二进制文件的原因。
17.4 二进制I/O类
*要点提示: *抽象类InputStream是读取二进制数据的根类,抽象类是OutputStream是写入二进制数据的根类。
注意: 二进制I/O类中所有方法都声明为抛出java.io.IOException或java.io.IOException的子类

17.4.1 FileInputStream和FileOutputStream
如果为一个不存在的文件创建一个FileInputStream对象,将会发生java.io.FileNotFountException异常
使用FileOutputStream构造方法创建对象时,如果这个文件不存在,就会创建一个新的文件
当文件已存在的时候,使用以下两个方法将会删除点文件中已经存在的内容:
1 | FileOutputStream(file: File) |
为了既保留文件现有内容又可以给文件追加新数据,可以在创建一个FileOutputStream对象时,添加append参数,并将其值设置为true。
几乎所有的I/O类都会抛出java.io.IOException。所以,必须在方法中声明会抛出java.io.IOException异常,或者将代码放到try-catch块中。
程序清单17-1使用二进制I/O将从1到10的10个字节值写入一个名为temp.dat的文件,再把他们从文件中读出来。
使用try-with-resource来声明和创建输入输出流,从而在使用后可以自动关闭。
java.io.InputStream和java.io.OutputStream实现了AutoClosable接口。
AutoClosable接口定义了close()方法,用来个关闭资源。任何AutoClosable类型的对象可以用于try-with-resources语法中,实现自动关闭。
二进制文件可以从程序中读取它,但是不能用文本编辑器阅读它。
提示:当流不再使用时,记得使用close()方法将其关闭,或者使用try-with-resource语句自动关闭。不关闭流可能会在输出文件中造成数据受损,或导致其他的程序设计错误。
*注意: *FileInputStream类的实例可以作为参数去构造一个Scanner=对象, FileOutputStream类的实例可以作为参数构造一个Printerwriter对象。可以创建一个PrintWriter对象来向文件中追加文本。如果xx.txt不存在,就会创建这个文件。如果xx.txt文件已经存在,就将新数据追加到该文件中。
1 | new Printer(new FileOutputStream("xx.txt", true)); |
FileterInputStream 和 FilterOutputStream
过滤器数据流(file stream)是为某种目的过滤字节的数据流。读取整数值、双精度值和字符串,那就需要一个过滤类来包装字节输入流。使用过滤器类就可以读取整数值、双精度值和字符串,而不是字节或字符。
FileterInputStream 和 FilterOutputStream是过滤数据的基类。需要处理基本数值类型时,就是用DataInputStream和DataOutputStream类来过滤字节。
###DataInputStream和DataOutputStream
基本类型的值不需要做任何转化就可以从内存复制到输出数据流。字符串的字符可以写成多种形式 :
- 二进制I/O中的字符与字符串
一个统一码由两个字节构成。writerChar(char c )方法将字符c的统一码写入输出流。writerChars(String s )方法将字符串s中的所有字符的统一码写到输出流中。writeBytes(String s )方法将字符串s中的每个字符的统一码的低字节写入到输出流。统一码的高字节被抛弃。
writeBytes(String s )方法适用于由ASCII码构成的字符串,ASCII码仅存储统一码的低字节。如果毕业字符串包含非ASCII码的字符,就必须使用writeChars方法实现写入这个字符串。
writeUTF(String s )方法将两个字节的长度信息写入输入流,后面紧跟着的是字符串s中的每个字符的改进版UTF-8的形式。
*警告: *
应该按存储的顺序和格式读取文件中的数据。
- 检测文件的末尾
如果达到InputStream的末尾之后还继续从中读取数据,就会发生EOFException异常。这个异常可以用来检查是否已经到达文件末尾。
BufferedInputStream 和 BufferedOutputStream
BufferedInputStream 和 BufferedOutputStream没有包含新的方法,它们的方法都是从InputStream 和OutputStream继承而来的。它们在后来管理了一个缓冲区,根据要求自动从磁盘中读取数据和写入数据。
缓冲区指定大小是512字节。
*提示: *应该总是使用缓冲区I/O来加速输入和输出。
17.5 示例学习: 复制文件
17.6 对象I/O
要点提示:ObjectInputStream 和ObjectOutputStream类可以用于读/写可序列化的对象。
ObjectInputStream 和ObjectOutputStream类除了实现基本数据类型与字符串的输入和输出之外,还可以实现对象的输入和输出。因而,可以使用ObjectInputStream 和ObjectOutputStream类代替DataInputStream和DataOutputStream。
读取时为了得到所需的数据类型,必须使用Java安全的类型转换。例如:
1 | Date date = (Date)(inputStream.readObject()); |
readObject()方法可能会抛出ClassNotFoundException.注意使用时要抛出它
17.6.1 Serializable 接口
可以写入到输出流中的对象被称为可序化的。
可序化对象的类必须实现Serializable接口。
Serializable是一个标记接口。它没有方法,不需要在类中为实现Serializable接口增加额外的代码。实现这个接口可以启动Java的序列化机制,自动完成存储对象和数组的过程。
Java提供一个内在机制自动完成写对象的过程。这个过程称为对象序列化(object serialization),它是在ObjectOutputStream中实现的。相反,读取对象的过程称作反对象序列化(object deserialization),它是在ObjectInputStream中实现的。
试图存储一个不支持Serializable接口的对象会引起一个NotSerializableexception异常。
当存储一个可序列化对象时,会对该对象的类进行编码。编码包括类名、类的签名、对象实例变量的值以及该对象引用的任何其他对象的闭团,但是不存储对象静态变量的值。
注意: 非序列化的数据域
如果一个对象是Serializable的实例,但它包含了非序列化的实例数据域,那么就不可以序列化这个对象。为了使该对象是可序列化的,需要给这些数据域加上关键字transient,告诉Java虚拟机将对象写入流时忽略这些数据域。看下面的例子:
注意: 重复的对象
如果一个对象不止一次写入对象流,不会存储对象的多分副本。第一次写入一个对象时,就会为它创建一个序列号。Java虚拟机将对象的所有内容和序列号一起写入对象流。以后每次存储时,如果再写入相同的对象,就只存储序列号。读出这些对象时,它们的引用相同,因为在内存中实际上存储的只是一个对象。
17.6.2 序列化数组
如果数组中所有元素都是可序列化的,那这个数组就是可序列化的。一个完整的数组可以使用writeObject方法写入文件,随后使用readObject方法恢复。
17.7 随机访问文件
*要点提示: *Java提供了RandomAccessFile类,允许从文件的任何位置进行数据的读写。
只读的流或者只写的流被称为顺序(sequential)流。使用顺序流打开的文件被称为顺序访问文件。顺序访问文件的内容不能更新。
使用RandomAccessFile类打开的文件称为随机访问文件。
RandomAccessFile类实现了DateInput和DataOutput接口。
创建一个RandomAccessFile时,可以指定两种模式(“r”,“rw”)。
1 | RandomAccessFile raf = new RandomAccessFile("test.dat", "rw"); |
如果test.dat 已经存在,则创建raf以便访问这个文件,否则就创建一个名为test.dat的新文件,再创建raf以便访问这个文件。raf.length()返回给定时刻文件test.dat中的字节数。向文件中追加数据,raf.length就会增加。
在RandomAccessFile中使用setLength(0)方法将文件长度设置为0。这样做的效果是将文件的原有内容删除。