0%

Java/二进制IO

二进制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是写入二进制数据的根类。

image-20200531193633640

image-20200531193655448

注意: 二进制I/O类中所有方法都声明为抛出java.io.IOException或java.io.IOException的子类

![image-20200531193942861](/Users/bowenkei/Library/Application Support/typora-user-images/image-20200531193942861.png)

17.4.1 FileInputStream和FileOutputStream

image-20200531194126051

如果为一个不存在的文件创建一个FileInputStream对象,将会发生java.io.FileNotFountException异常

image-20200531194506941

使用FileOutputStream构造方法创建对象时,如果这个文件不存在,就会创建一个新的文件

当文件已存在的时候,使用以下两个方法将会删除点文件中已经存在的内容:

1
2
FileOutputStream(file: File)
FileOutputStream(filename: String)

为了既保留文件现有内容又可以给文件追加新数据,可以在创建一个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

image-20200601195913906

image-20200601195934714

基本类型的值不需要做任何转化就可以从内存复制到输出数据流。字符串的字符可以写成多种形式 :

  1. 二进制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的形式。

image-20200601201352901

*警告: *

应该按存储的顺序和格式读取文件中的数据。

  1. 检测文件的末尾

如果达到InputStream的末尾之后还继续从中读取数据,就会发生EOFException异常。这个异常可以用来检查是否已经到达文件末尾。

BufferedInputStream 和 BufferedOutputStream

BufferedInputStream 和 BufferedOutputStream没有包含新的方法,它们的方法都是从InputStream 和OutputStream继承而来的。它们在后来管理了一个缓冲区,根据要求自动从磁盘中读取数据和写入数据。

image-20200602160930843

image-20200602161036165

缓冲区指定大小是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虚拟机将对象写入流时忽略这些数据域。看下面的例子:

image-20200602201849302

注意: 重复的对象

如果一个对象不止一次写入对象流,不会存储对象的多分副本。第一次写入一个对象时,就会为它创建一个序列号。Java虚拟机将对象的所有内容和序列号一起写入对象流。以后每次存储时,如果再写入相同的对象,就只存储序列号。读出这些对象时,它们的引用相同,因为在内存中实际上存储的只是一个对象。

17.6.2 序列化数组

如果数组中所有元素都是可序列化的,那这个数组就是可序列化的。一个完整的数组可以使用writeObject方法写入文件,随后使用readObject方法恢复。

17.7 随机访问文件

*要点提示: *Java提供了RandomAccessFile类,允许从文件的任何位置进行数据的读写。

只读的流或者只写的流被称为顺序(sequential)流。使用顺序流打开的文件被称为顺序访问文件。顺序访问文件的内容不能更新。

使用RandomAccessFile类打开的文件称为随机访问文件。

RandomAccessFile类实现了DateInput和DataOutput接口。

image-20200603110915507

创建一个RandomAccessFile时,可以指定两种模式(“r”,“rw”)。

1
RandomAccessFile raf = new RandomAccessFile("test.dat", "rw");

如果test.dat 已经存在,则创建raf以便访问这个文件,否则就创建一个名为test.dat的新文件,再创建raf以便访问这个文件。raf.length()返回给定时刻文件test.dat中的字节数。向文件中追加数据,raf.length就会增加。

image-20200603111803022

在RandomAccessFile中使用setLength(0)方法将文件长度设置为0。这样做的效果是将文件的原有内容删除。