文章目录
- 1.二进制文件
- 2.二进制 I/O 类
- 2.1 FileInputStream 和 FileOutputStream
- 2.2 FilterInputStream和 FilterOutputStream
- 2.3 DatalnputStream 和 DataOutputStream
- 2.4 BufferedInputStream 和 BufferedOutputStream
- 2.5 ObjectInputStream 和 ObjectOutputStream
- 2.6 Serializable接口
- 2.7 序列化数组
- 3.随机访问文件
1.二进制文件
文件可以分为文本或者二进制的。可以使用文本编辑器,比如 Windows 下的记事本或者 UNIX 下的 vi 编辑器,进行处理(读取、创建或者修改)的文件称为文本文件。所有其他的文件称为二进制文件。
不能使用文本编辑器来读取二进制文件一它们被设计为使用程序来读取。例如,Java 源程序存储在文本文件中,可以使用文本编辑器读取,而 Java 类文件是二进制文件,由 Java 虚拟机读取。
计算机并不区分二进制文件和文本文件。所有的文件都是以二进制形式来存储的,因此,从本质上说,所有的文件都是二进制文件。文本 I/O 建立在二进制 I/O 的基础之上,它能提供一层抽象,用于字符的编码和解码。
对于文本I/O,编码和解码是自动进行的。在写入字符时,Java 虚拟机会将 Unicode 码转化为文件特定的编码,而在读取字符时,将文件特定的编码转化为 Unicode 码。
二进制 I/O 不需要转化。如果使用二进制 I/O 向 文件写入一个数值,就是将内存中的那个值复制到文件中。
由于二进制 I/O 不需要编码和解码,所以,它比文本 I/O 效率高。二进制文件与主机的编码方案无关,因此,它是可移植的。任何机器上的 Java 程序都可以读取 Java 程序所创建的二进制文件。这就是为什么 Java 的类文件存储为二进制文件的原因。Java 类文件可以在任何具有 Java 虚拟机的机器上运行。
一般来说,对于文本编辑器或文本输出程序创建的文件,应该使用文本输入来读取,对于 Java 二进制输出程序创建的文件,应该使用二进制输入来读取。
2.二进制 I/O 类
2.1 FileInputStream 和 FileOutputStream
几乎所有的 I/O 类中的方法都会抛出异常java.io.IOException。因此,必须在方法中声明会抛出java.io.工OException异常,或者将代码放到 try-catch 块中。
import java.oi.*;
public class TestFileStream{
public static void main(String[] args){
try{
FileOutputStream output = new FileOutputStream("temp.txt");
}{
for(int i = 0;i<10;i++)
output.write(i);
}
try{
FileInputStream input = new FileInputStream("temp.txt");
}{
int value;
while((value=input.read())!=1)
System.out.print(value + " ");
}
}
}
2.2 FilterInputStream和 FilterOutputStream
过滤器数据流 ( filterstream) 是为某种目的过滤字节的数据流。基本字节输入流提供的读取方法 read 只能用来读取字节。
如果要读取整数值、双精度值或字符串,那就需要一个过滤器类来包装字节输入流。使用过滤器类就可以读取整数值、双精度值和字符串,而不是
字节或字符。
FilterlnputStream 类和 FilterOutputStream 类是用于过滤数据的基类。需要处理基本数值类型时,就使用 DatalnputStream 类和 DataOutputStream 类来过滤字节。
2.3 DatalnputStream 和 DataOutputStream
DatalnputStream 从数据流读取字节,并且将它们转换为合适的基本类型值或字符串。
DatabutputStream 将基本类型的值或字符串转换为字节, 并且将字节输出到流。
如果到达 Inputstream 的末尾之后还继续从中读取数据,就会产生 EOFException 异常。这个异常可以用来检查是否已经到达文件末尾。
import java.io.*;
public class Write{
public static void main(String[] args){
try{
try(new DataOutputStream(new FileOutputStream("test.dat"))){
output.writeDouble(4.5);
}
try(DatalnputStream input =new DataInputStream(new Fi1eInputStream("test .dat"))){
while(true)
System.out.println(input.readDouble());
}
}
catch(EOFException ex){
System.out.println("All data were read");
}
catch(IOException ex){
ex.printStackTrace();
}
}
}
2.4 BufferedInputStream 和 BufferedOutputStream
BufferedlnputStream 类和 BufferedOutputStream 类可以通过减少磁盘读写次数来提高输入和输出的速度。使用 BufferdlnputStream 时,磁盘上的整块数据一次性地读入内存的缓冲区中。然后从缓冲区中将单个数据传递到程序中。
DataOutputStream output = new DataOutputStream(
new BufferedOutputStream (new FileOutputStream("temp.dat"))) ;
DatalnputStream input = new DataInputStream(
new BufferedInputStream(new FileInputStream("temp.dat")))
2.5 ObjectInputStream 和 ObjectOutputStream
DatalnputStream 类和 DataOutputStream 类可以实现基本数据类型与字符串的输入和输出。而 ObjectlnputStream 类和 ObjectOutputStream 类除了可以 实现基本数据 类型与字符串的输入 和输出之外,还可以实现对象的输入和输出。
由于 ObjectlnputStream 类和ObjectOutputStream 类包含 DatalnputStream 类 和 DataOutputStream 类 的所有 功能,所以,完全可以用 ObjectInputStream 类和 ObjectOutputStream 类代替 DatalnputStream 类和DataOutputStream类。
import java.io.*;
public class Test{
public static void main(String[] args)
throws IOException{
try{
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("o.dat"));
// 可以改为
// ObjectOutputStream output = new ObjectOutputStream(new BufferedOutputStream(new Fi1eOutputstream ( "o.dat")));
}{
output.writeUTF("John");
output.writeDouble(85.5);
output.writeObject(new java.util.Date());
}
try{
ObjectInputStream input = new ObjectInputStream(FileInoutStream("o.dat"));
}{
String name = input.readUTF();
double score = input.readDouble();
java.util.Date = (java.util.Date)(input.readObject());
}
}
}
2.6 Serializable接口
并不是每一个对象都可以写到输出流。可以写到输出流中的对象称为可序列化的( serializable)。因为可序列化的对象是 java.io.Serializable 接口的实例,所以, 可序列化对象的类必须实现 Serializable 接口
Java 提供一个内在机制自动完成写对象的过程。这个过程称为对象序列化 ( object serialization), 它是在 ObjectOutputStream中实现的。与此相反,读取对象的过程称作对象反序列化 ( object deserialization), 它是在Objectinputstream 类中实现的。
当存储一个可序列化对象时,会对该对象的类进行编码。编码包括类名、类的签名、对象实例变量的值以及该对象引用的任何其他对象的闭包,但是不存储对象静态变量的值。
如果一个对象是 Serializable 的 实例,但它包含了不能序列化的实例数据域,那么可以序列化这个对象吗?答案是否定的。为了使该对象是可序列化的,需要给这些数据域加上关键字 transient, 告诉 Java 虚拟机将对象写入对象流时忽略这些数据域
class A implements java.io.Serializable{
// 只需序列 化变量 因为 v2 是一个静态变量,所以没有序列化。因为 v3 标记为 transient, 所以也没有序列化
private int v1;
private static double v2;
private transient B v3 = new B();
}
如果一个对象不止一次写入对象流,会存储对象的多份副本吗?答案是不会。第一次写入一个对象时,就会为它创建一个序列号。Java 虚拟机将对象的所有内容和序列号一起写入对象流。以后每次存储时,如果再写入相同的对象,就只存储序列号。读出这些对
象时,它们的引用相同,因为在内存中实际上存储只是一个对象。
2.7 序列化数组
如果数组中的所有元素都是可序列化的,这个数组就是可序列化的。整个数组可以用writeObject 方法存入文件,随后用 readobject 方法恢复。
int[] numbers = {1, 2, 3, 4, 5};
String[] strings = {"John0", "Susan" ,
try (ObjectOutputstream output = newObjectOutputStream(new FlieOutputstream( "array.dat", true));
){
output.writeObject(numbers);
output.writeObject(strings);
}
try (Objectinputstream input =
new ObjectInputStream(new Fl1elnputStream("array.dat")) ;
) {
int[] newNumbers = (int[ ] ) (input.readObject()) ;
Stringf] newStrings = (String[]) (input .readObject()) ;
for(int i = 0; i < newNumbers.1ength; i++)
System.out.print(newNumbers[1] + " ");
System.out.println() ;
for(int i = 0; i < newStrings.1ength; i++)
System.out .print(newStr1ngs[i ] + " ") ;
}
3.随机访问文件
到现在为止,所使用的所有流都是只读的或只写的这些流称为顺序 (sequential) 流。使用顺序流打开的文件称为顺序访问文件。顺序访问文件的内容不能更新。然而, 经常需要修改文件。Java 提供RandomAccessFile 类,允许在文件的任意位置上进行读写。使用 RandomAccessFile类打开的文件称为随机访问文件。
当创建一个 RandomAccessFile 时, 可以指定两种模式 ( “r” 或 “rw”) 之一。模式 "r"表明这个数据流是只读的. 模式 ”rw” 表明这个数据流既允许读也允许写。
RandomAccessFile raf = new RandomAccessFile("test.dat","rw");
如果文件 test.dat 已经存在,则创建 raf 以便访问这个文件;如果 test.dat 不存在,则创建一个名为 test.dat 的新文件,再创建 raf 来访问这个新文件。
随机访问文件是由字节序列组成的。一个称为文件指针 (file pointer) 的特殊标记定位这些字节中的某个位置。文件的读写操作就是在文件指针所指的位置上进行的。打开文件时,文件指针置于文件的起始位置。在文件中进行读写数据后,文件指针就会向前移到下一个数据项。