此系列文章翻译自Jakob Jenkov的java系列教程,原文地址链接为Jakob Jenkov的教程,教程比较详细,很适合初学者!
您可以查看 Java IO 经典教程 (上) (翻译自jenkov.com)
您可以查看 Java IO 经典教程 (下) (翻译自jenkov.com)如果您更喜欢简书的风格,也可以点击链接:此文章简书链接
PipedInputStream
PipedInputStream会以字节流的形式来读取管道的内容。同一JVM下的线程间通讯可以用到管道。管道的更多内容可以去查看前面的章节。
PipedInputStream例子
下面是一个相关的例子:
InputStream input = new PipedInputStream(pipedOutputStream);
int data = input.read();
while(data != -1) {
//do something with data...
doSomethingWithData(data);
data = input.read();
}
input.close();
read()方法返回一个int值,为每次读取的字节。如果返回的是 -1,那么代表已经读取完毕。
关于PipedInputStream更多的内容
相关方法可以参考InputStream,因为它是InputStream的子类。关于管道的更多内容,可以参开前面的管道章节。
PipedOutputStream
PipedOutputStream可以以字节流的形式写出到java 管道。管道用来同一JVM下的不同线程间的通讯。
PipedOutputStream例子
下面是一个简单的PipedOutputStream例子:
OutputStream output = new PipedOutputStream(pipedInputStream);
while(moreData) {
int data = getMoreData();
output.write(data);
}
output.close();
write()方法的返回值写出去的字节。
PipedOutputStream更多方法
PipedOutputStream是OutputStream的子类,所以他们有相同的基础方法,所以可以参考OutputStream的相关内容
ByteArrayInputStream
ByteArrayInputStream类可以让你从一个字节数组来读取流,下面是一个例子:
byte[] bytes = ... //get byte array from somewhere.
InputStream input = new ByteArrayInputStream(bytes);
int data = input.read();
while(data != -1) {
//do something with data
data = input.read();
}
input.close();
可以处理你保存在数组里面的数据,并且你有一个组件只能处理流。所以ByteArrayInputStream可以处理字节数组,并写到到流中。
ByteArrayOutputStream
Java IO API的ByteArrayOutputStream类允许您捕获写入到一个数组中的流的数据。你把数据写到ByteArrayOutputStream,写完之后,调用toByteArray()方法就可以以字节数组的形式获得所有的已写的数据。
ByteArrayOutputStream例子
下面是一个简单例子:
ByteArrayOutputStream output = new ByteArrayOutputStream();
//write data to output stream
byte[] bytes = output.toByteArray();
ByteArrayOutputStream应用的场景是,当你有一个组件需要把数据写出到OutputStream,但是你需要用到字节数组。
FilterInputStream
FilterInputStream是实现你自己的过滤输入流的基础类。基本上它只是覆盖了InputStream的方法,调用FilterInputStream的方法实际上就是调用包装的InputStream。InputStream在FilterInputStream的构造方法上被传进去,就像下面的这样:
FilterInputStream inputStream = new FilterInputStream(new FileInputStream("c:\\myfile.txt"));
FilterInputStream并没有什么特殊的地方。它打算称为你自己的子类的基类,但是以我的想法,你完全可以直接继承InputStream。
以我的观点,我并没有看见这个类的明确目的。也没有看到这个类在InputStream中添加任何改变行为,只是在它的构造函数中需要一个InputStream。
FilterOutputStream
FilterInputStream是实现你自己的过滤输出流的基础类。基本上它只是覆盖了InputStream的方法。
以我的观点,我并没有看见这个类的明确目的。也没有看到这个类在OutputStream中添加任何改变行为,只是在它的构造函数中需要一个OutputStream。如果你选择这个这个类那不如直接继承OutputStream的好,避免类的层次节后出现混乱。
BufferedInputStream
BufferedInputStream为你的输入流提供了一个缓冲区。缓冲区可以大大的提高IO速度。不是每次从网络或磁盘上读取一个字节,而是每次读取一大块儿内容到内部的缓冲区中。当你从BufferedInputStream读取一个字节时,你其实是从它内部的缓冲区读取的。当缓冲区已经读完,BufferedInputStream会读另一大块的数据到缓冲区中。这通常要比每次读取单字节要快的多,尤其是访问磁盘和大数据量的情况。
BufferedInputStream例子
向InputStream增加一个buffer,只是用BufferedInputStream包装一下:
InputStream input = new BufferedInputStream(
new FileInputStream("c:\\data\\input-file.txt"));
就像你看到的,用BufferedInputStream去给InputStream增加一个buffer是如此简单。BufferedInputStream内部创建了一个字节数组,在InputStream底层调用InputStream.read(byte[])方法来填充数组。
为BufferedInputStream设置buffer大小
你可以设置buffer大小,以便在BufferedInputStream中使用。你可以在构造方法中提供此参数:
int bufferSize = 8 * 1024;
InputStream input = new BufferedInputStream(
new FileInputStream("c:\\data\\input-file.txt"),
bufferSize
);
上面例子中,设置了BufferedInputStream内部缓冲区为 8 KB。buffer大小的最佳设置为 1024 字节的倍数。这在磁盘上等内置缓冲效果最好。
除了给你的输入流增加buffer以外,BufferedInputStream与InputStream完全一样。
BufferedInputStream的最佳buffer大小
你可以做一些实验,去使用不同大小的buffer来确定在你的硬件上哪种buffer尺寸可以提供最高性能。最佳buffer尺寸依赖于你使用的是磁盘还是网络InputStream。
不管是磁盘还是网络流,最佳的buffer大小也可能取决于具体计算机硬件。如果每次读取磁盘内容至少 4KB,那么设置 4KB 以下的buffer大小是不明智的。更好的是设置buffer大小为 4KB 的倍数。实际上设置为 6KB 同样不明智。
即使你的磁盘读取块大小比如是 4KB 每次,使用一个大于这个的缓冲区仍然是一个好主意。磁盘善于按顺序读取数据—这意味着善于读取多个并且他们有先后顺序的块。因此,使用 16KB buffer,或者 64KB(或更大)仍然比 4KB buffer更能给你一个比较好的性能。
还要记住,一些磁盘有兆字节大小的读取缓存。如果你将文件放入内部缓存,您也可以使用一个读操作将所有这些数据都放到BufferedInputStream,用来代替多个读操作。你有可能会担心在读的过程中缓存会有被擦除的风险,从而导致硬盘重新读取该块到缓存中
要找到最佳的BufferedInputStream buffer大小,就要找到磁盘读取的块大小,还有缓存大小,然后让buffer大小为这个的整数倍。你一定要亲自去实验才能找到最佳buffer大小。通过测量不同buffer大小的读取速度来实现这个。
mark() 和 reset()
关于BufferedInputStream一个有趣的地方是它支持从InputStream继承的mark() 和 reset()方法。并不是InputStream所有的子类都支持这两个方法。通常可以调用markSupported()方法去查看是否支持mark() 和 reset()方法,但是BufferedInputStream支持它们
BufferedOutputStream
BufferedOutputStream 为你的输出流提供了一个缓冲区。缓冲区可以大大的提高IO速度。不是每次从网络或磁盘上读取一个字节,而是每次读取一大块儿内容到内部的缓冲区中。这通常要比每次读取单字节要快的多,尤其是访问磁盘和大数据量的情况。
向OutputStream增加一个buffer,只是用BufferedOutputStream包装一下:
OutputStream output = new BufferedOutputStream(
new FileOutputStream("c:\\data\\output-file.txt"));
为BufferedOutputStream设置buffer大小
你可以设置buffer大小,以便在BufferedOutputStream中使用。你可以在构造方法中提供此参数:
int bufferSize = 8 * 1024;
OutputStream output = new BufferedOutputStream(
new FileOutputStream("c:\\data\\output-file.txt"),
bufferSize
);
上面例子中,设置了BufferedOutputStream内部缓冲区为 8 KB。buffer大小的最佳设置为 1024 字节的倍数。这在磁盘上等内置缓冲效果最好。
除了给你的输出流增加buffer以外,BufferedOutputStream与OutputStream完全一样。
BufferedInputStream的最佳buffer大小
此章节内容和上一节“BufferedInputStream”内容完全一致。
PushbackInputStream
PushbackInputStream是在你从InputStream解析数据时候使用。有时候在你决定如何处理当前字节之前,你需要提前读几个字节来确定后面的内容是什么。PushbackInputStream可以让你实现上面的操作。实际上,它允许你把字节推回到流中。这些字节会有下次被你重新读取。
PushbackInputStream例子
下面是一个PushbackInputStream的简单例子:
PushbackInputStream input = new PushbackInputStream(
new FileInputStream("c:\\data\\input.txt"));
int data = input.read();
input.unread(data);
调用read()方法和普通InputStream一样。调用unread()方法把字节推回到PushbackInputStream中。下次调用read()方法时被推回的字节首先会被读取。如果你推回多个字节,最后被推回的字节会先被读取,就像栈结构一样。
设置PushbackInputStream的推回限制
你可以在PushbackInputStream的构造方法中设置推回的字节数量。下面是一个例子:
int pushbackLimit = 8;
PushbackInputStream input = new PushbackInputStream(
new FileInputStream("c:\\data\\input.txt"),
pushbackLimit);
例子中设置了一个8字节长度的内部缓冲区。这意味你每次最多能推回8个字节,在你下次读之前。
SequenceInputStream
SequenceInputStream可以将两个或更多个InputStream合并成一个。它会首先读取第一个的全部字节,然后第二个。这就是叫SequenceInputStream的原因,因为它是安顺序读取的。
SequenceInputStream例子
是时候通过一个例子来看如何使用它了。在使用之前得先在类里面导入这个:
import java.io.SequenceInputStream;
下面让我们来看一个具体的例子:
InputStream input1 = new FileInputStream("c:\\data\\file1.txt");
InputStream input2 = new FileInputStream("c:\\data\\file2.txt");
SequenceInputStream sequenceInputStream =
new SequenceInputStream(input1, input2);
int data = sequenceInputStream.read();
while(data != -1){
System.out.println(data);
data = sequenceInputStream.read();
}
例子中的java代码首先实例化两个InputStream。FileInputStream继承自InputStream,所以它们可以用在sequenceInputStream中。
然后,例子创建了一个SequenceInputStream实例。利用两个参数的构造方法,这两个参数为InputStream类型,这就是如何合并两个输入流的办法。
两个InputStream实例可以被SequenceInputStream合并在一起就像一个连贯的流。当第二个流都被写过之后,SequenceInputStream的read()方法就会返回 -1,就是其他的输入流一样。
合并更多数量的流
你可以有两种办法来来并不更多的流。第一种办法就是把所有的实例放入一个Vector,然后把Vector传给SequenceInputStream的构造方法。下面是其示例:
InputStream input1 = new FileInputStream("c:\\data\\file1.txt");
InputStream input2 = new FileInputStream("c:\\data\\file2.txt");
InputStream input3 = new FileInputStream("c:\\data\\file3.txt");
Vector<InputStream> streams = new Vector<>();
streams.add(input1);
streams.add(input2);
streams.add(input3);
SequenceInputStream sequenceInputStream =
new SequenceInputStream(streams.elements()))
int data = sequenceInputStream.read();
while(data != -1){
System.out.println(data);
data = sequenceInputStream.read();
}
sequenceInputStream.close();
第二种办法是把InputStream合并至SequenceInputStream,再将两个InputStream合并至另一个SequenceInputStream,最后将两个SequenceInputStream合并到一个SequenceInputStream中:
SequenceInputStream sequenceInputStream1 =
new SequenceInputStream(input1, input2);
SequenceInputStream sequenceInputStream2 =
new SequenceInputStream(input3, input4);
SequenceInputStream sequenceInputStream =
new SequenceInputStream(
sequenceInputStream1, sequenceInputStream2)){
int data = sequenceInputStream.read();
while(data != -1){
System.out.println(data);
data = sequenceInputStream.read();
}
sequenceInputStream.close();
关闭SequenceInputStream
从SequenceInputStream读取完数据后要记得关闭它。关闭的同时也会关闭在读的InputStream示例。关闭只需要调用close()方法,就像下面的一样:
sequenceInputStream.close();
在java7中你也可以用try-with-resources结构。下面代码为如何使用此种结构来关闭流:
InputStream input1 = new FileInputStream("c:\\data\\file1.txt");
InputStream input2 = new FileInputStream("c:\\data\\file2.txt");
try(SequenceInputStream sequenceInputStream =
new SequenceInputStream(input1, input2)){
int data = sequenceInputStream.read();
while(data != -1){
System.out.println(data);
data = sequenceInputStream.read();
}
}
注意这并没有任何显式的调用close()方法。
也要注意FileInputStream的两个示例并没有放在try-with-resources代码块里。这意味着try-with-resources并不会自动关闭两个FileInputStream。然而,当SequenceInputStream被关闭后它也会关闭它读的InputStream,所以FileInputStream也会在SequenceInputStream关闭后被关闭。
DataInputStream
DataInputStream可以让你从InputStream读取Java基本类型来代替原始的字节。用DataInputStream来包装InputStream,你就可以从DataInputStream直接以Java基本类型来读取数据。这就是为什么叫做DataInputStream。
如果你需要读取的数据是由大于一个字节的java基础类型构成,比如int, long, float, double等,那么用DataInputStream是很方便的。DataInputStream希望的数据是写入到网络的有序多字节数据。
你经常会使用一个DataInputStream去读DataOutputStream写好的数据。
DataInputStream例子
下面是一个DataInputStream的例子:
DataInputStream dataInputStream = new DataInputStream(
new FileInputStream("binary.data"));
int aByte = input.read();
int anInt = input.readInt();
float aFloat = input.readFloat();
double aDouble = input.readDouble();
//etc.
input.close();
例子中首先创建了一个DataInputStream实例并发数据源FileInputStream实例传进去。然后Java基本类型就可以读出来了。
使用DataInputStream时同时使用DataOutputStream
就像上面提及的,DataInputStream和DataOutputStream经常同时被使用。因此我只是想给你展示一个例子,先用DataOutputStream来写数据然后再用DataInputStream来读数据。下面是相关的Java代码:
import java.io.*;
public class DataInputStreamExample {
public static void main(String[] args) throws IOException {
DataOutputStream dataOutputStream =
new DataOutputStream(
new FileOutputStream("data/data.bin"));
dataOutputStream.writeInt(123);
dataOutputStream.writeFloat(123.45F);
dataOutputStream.writeLong(789);
dataOutputStream.close();
DataInputStream dataInputStream =
new DataInputStream(
new FileInputStream("data/data.bin"));
int int123 = dataInputStream.readInt();
float float12345 = dataInputStream.readFloat();
long long789 = dataInputStream.readLong();
dataInputStream.close();
System.out.println("int123 = " + int123);
System.out.println("float12345 = " + float12345);
System.out.println("long789 = " + long789);
}
}
这个例子首先创建了一个DataOutputStream,写int,float和long值。然后创建DataInputStream实例去同一个文件读int, float和long值。
reads the int, float and long value in from the same file.
关闭DataInputStream
读取完数据的时候,你要记住去关闭它。关闭DataInputStream也会关闭它读的InputStream。这些需要调用close()方法:
dataInputStream.close();
你也可以在Java7中使用try-with-resources结构。下面是介绍如何使用try-with-resources结构来关闭流:
InputStream input = new FileInputStream("data/data.bin");
try(DataInputStream dataInputStream =
new DataInputStream(input)){
int data = dataInputStream.readInt();
int int123 = dataInputStream.readInt();
float float12345 = dataInputStream.readFloat();
long long789 = dataInputStream.readLong();
}
注意这并没有任何显式的调用close()方法。
也要注意创建FileInputStream示例并没有放在try-with-resources代码块里。这意味着try-with-resources并不会自动关闭FileInputStream。然而,当DataInputStream被关闭后它也会关闭它读的InputStream,所以FileInputStream也会在DataInputStream关闭后被关闭。
DataOutputStream
DataOutputStream可以让你从OutputStream写出Java基本类型来代替原始的字节。用DataOutputStream来包装OutputStream,你就可以用DataOutputStream直接以Java基本类型来写数据。这就是为什么叫做DataOutputStream。
你经常会在使用DataOutputStream的同时也使用DataInputStream。你使用DataOutputStream写将数据写入到一个示例文件,然后再用DataInputStream去读。这些会在下面的例子中开展示。
使用DataOutputStream和DataInputStream是一种方便的方式,可以将比字节更大的Java基本类型写入OutputStream,并能够再次读取它们,从而确保使用正确的字节顺序。
DataOutputStream例子
下面是一个DataOutputStream相关的例子:
DataOutputStream dataOutputStream = new DataOutputStream(
new FileOutputStream("binary.data"));
dataOutputStream.write(45); //byte data
dataOutputStream.writeInt(4545); //int data
dataOutputStream.writeDouble(109.123); //double data
dataOutputStream.close();
这个例子首先创建了一个DataOutputStream实例,并传入一个FileOutputStream。然后,例子分别写了一个byte,int,Double的数据。在最后,将流关闭。
使用DataOutputStream时使用DataInputStream
就像上面提及的,DataInputStream和DataOutputStream经常同时被使用。因此我给你展示一个例子,先用DataOutputStream来写数据然后再用DataInputStream来读数据。下面是相关的Java代码:
import java.io.*;
public class DataOutputStreamExample {
public static void main(String[] args) throws IOException {
DataOutputStream dataOutputStream =
new DataOutputStream(
new FileOutputStream("data/data.bin"));
dataOutputStream.writeInt(123);
dataOutputStream.writeFloat(123.45F);
dataOutputStream.writeLong(789);
dataOutputStream.close();
DataInputStream dataInputStream =
new DataInputStream(
new FileInputStream("data/data.bin"));
int int123 = dataInputStream.readInt();
float float12345 = dataInputStream.readFloat();
long long789 = dataInputStream.readLong();
dataInputStream.close();
System.out.println("int123 = " + int123);
System.out.println("float12345 = " + float12345);
System.out.println("long789 = " + long789);
}
}
这个例子首先创建了一个DataOutputStream,写int,float和long值。然后创建DataInputStream实例去同一个文件读int, float和long值。
关闭DataOutputStream
读取完数据的时候,你要记住去关闭它。关闭DataInputStream也会关闭它写的OutputStream。这些需要调用close()方法:
dataOutputStream.close();
你也可以在Java7中使用try-with-resources结构。下面是介绍如何使用try-with-resources结构来关闭流:
OutputStream output = new FileOutputStream("data/data.bin");
try(DataOutputStream dataOutputStream =
new DataOutputStream(output)){
dataOutputStream.writeInt(123);
dataOutputStream.writeFloat(123.45F);
dataOutputStream.writeLong(789);
}
注意这并没有任何显式的调用close()方法。
也要注意创建FileOutputStream示例并没有放在try-with-resources代码块里。这意味着try-with-resources并不会自动关闭FileOutputStream。然而,当DataOutputStream被关闭后它也会关闭它读的OutputStream,所以FileOnputStream也会在DataOutputStream关闭后被关闭。
PrintStream
Java的PrintStream类(java.io.PrintStream)可以让你将格式化数据写入到OutputStream底层。可以格式化Java基本数据类型,比如int,long等。格式化成文本而不是成字节。这就是为什么称为PrintStream。
PrintStream例子
下面是一个关于PrintStream的例子:
PrintStream printStream = new PrintStream(outputStream);
printStream.print(true);
printStream.print((int) 123);
printStream.print((float) 123.456);
printStream.close();
首先创建一个PrintStream实例并在构造方法中传入OutputStream。然后打印了三个Java基本类型的数据。最后关闭流。
为了简便起见,在示例中省略了PrintStream所写的输出流的实例。PrintStream有很多构造方法,可以以File作为参数,也可以是OutputStream等。
System.out和System.err都是PrintStream
你可能熟悉Java中两个著名的PrintStream实例:System.out 和 System.err。如果用过上面两个实例,那么说明你已经用过PrintStream了。
printf()
Java PrintStream类有两个强大的方法format() 和 printf()(他们实际上做的事是一样的,但是”printf”对于 C 程序员来说更熟悉一些)。这些方法可以让你非常高效的混合文本和数据、使用格式化字符。
下面是一个printf()的例子:
PrintStream printStream = new PrintStream(outputStream);
printStream.printf(Locale.UK, "Text + data: %1$", 123);
printStream.close();
更多的format() 和 printf()相关用法可以参考Java官方文档。
关闭PrintStream
写完数据的时候要记得关闭流。关闭流的同时,也会关闭OutputStream的实例。关闭流可以调用它的close()方法:
printStream.close();
你也可以使用try-with-resources结构。下面是如何使用try-with-resources结构来关闭流:
OutputStream output = new FileOutputStream("data/data.bin");
try(PrintStream printStream =
new PrintStream(output)){
printStream.writeInt(123);
printStream.writeFloat(123.45F);
printStream.writeLong(789);
}
ObjectInputStream
ObjectInputStream(java.io.ObjectInputStream)可以从InputStream读取Java对象来代替原始的字节。用ObjectInputStream来包装InputStream然后就可以从它里面直接读取对象。当然,读取的字节必须是有效且可序列化的Java对象,否则就会读取失败。
一般情况下你会用ObjectInputStream读取对象,用ObjectOutputStream写对象(序列化)。稍后会给出相关的例子。
ObjectInputStream例子
下面是Java ObjectInputStream 例子:
ObjectInputStream objectInputStream =
new ObjectInputStream(new FileInputStream("object.data"));
MyClass object = (MyClass) objectInputStream.readObject();
//etc.
objectInputStream.close();
这个例子中你读取的对象必须是 MyClass的实例,并且已经被序列化到了文件object.data中通过ObjectOutputStream。
在你序列化或反序列化之前,你必须已经实现了java.io.Serializable接口。更多的信息可以参考后面的文章 Java Serializable。
ObjectInputStream 和 ObjectOutputStream同时使用
在文章的开始我已经说要展示一个ObjectInputStream 和 ObjectOutputStream一起使用的例子,那么下面就是:
import java.io.*;
public class ObjectInputStreamExample {
public static class Person implements Serializable {
public String name = null;
public int age = 0;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(new FileOutputStream("data/person.bin"));
Person person = new Person();
person.name = "Jakob Jenkov";
person.age = 40;
objectOutputStream.writeObject(person);
objectOutputStream.close();
ObjectInputStream objectInputStream =
new ObjectInputStream(new FileInputStream("data/person.bin"));
Person personRead = (Person) objectInputStream.readObject();
objectInputStream.close();
System.out.println(personRead.name);
System.out.println(personRead.age);
}
}
例子首先创建了一个ObjectOutputStream实例并把一个FileOutputStream传入构造方法。然后创建了一个 Person 实例并将其写到ObjectOutputStream,然后关闭流。
这个例子运行的结果就是:
Jakob Jenkov
40
关闭ObjectInputStream
关闭流的做法和上面文章中此处的内容几乎一致,可以参考
ObjectOutputStream
ObjectOutputStream(java.io.ObjectOutputStream)可以从OutputStream写出Java对象来代替原始的字节。用ObjectOutputStream来包装OutputStream然后就可以向其中写入对象。
Java ObjectOutputStream经常会和Java ObjectInputStream一起使用。稍后会展示一个相关的例子。
ObjectOutputStream例子
下面是关于Java ObjectOutputStream的例子:
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(new FileOutputStream("object.data"));
MyClass object = new MyClass();
output.writeObject(object);
output.close();
例子首先创建了一个ObjectOutputStream实例,向构造参数传入一个FileOutputStream。然后创建了一个MyClass实例并将其写出去。最后关闭流。
在进行序列化和反序列化之前,你要实现java.io.Serializable接口。
ObjectOutputStream 和 ObjectInputStream同时使用
下面是两者一起使用的例子:
import java.io.*;
public class ObjectInputStreamExample {
public static class Person implements Serializable {
public String name = null;
public int age = 0;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(new FileOutputStream("data/person.bin"));
Person person = new Person();
person.name = "Jakob Jenkov";
person.age = 40;
objectOutputStream.writeObject(person);
objectOutputStream.close();
ObjectInputStream objectInputStream =
new ObjectInputStream(new FileInputStream("data/person.bin"));
Person personRead = (Person) objectInputStream.readObject();
objectInputStream.close();
System.out.println(personRead.name);
System.out.println(personRead.age);
}
}
例子首先创建了一个ObjectOutputStream实例并向构造方法传入FileOutputStream。然后创建了一个 Person 实例并将其写到ObjectOutputStream,然后关闭流。
然后创建了一个ObjectInputStream实例,并且连接到和ObjectOutputStream同一文件。然后利用对象从ObjectInputStream读取数据并将结果强转成Person。随后关闭输入流并打印结果。
Serializable
Java Serializable接口(java.io.Serializable),是一个标记接口,你的类如果想被序列化与反序列化就必须要实现此接口。Java对象序列化用ObjectOutputStream实现,反序列化(写)由ObjectInputStream来完成。
Serializable是一个标记接口并且内部没有方法。因此一个类实现这个接口后不需要去实现任何方法。实现这个接口只是告诉Java序列化类这个类是可以进行对象序列化的。
Serializable例子
下面是关于实现Serializable接口的例子:
import java.io.Serializable;
public static class Person implements Serializable {
public String name = null;
public int age = 0;
}
如你所见,People类实现了Serializable接口,没有实现任何该接口的方法。像之前提到的,Java Serializable接口只是一个标记接口,不需要实现任何方法。
查看完整的例子可以去看 ObjectInputStream 或 ObjectOutputStream章节。
serialVersionUID
类除了实现这个接口以外,也要去定义一个私有的静态 long 类型的serialVersionUID变量。
下面是之前的那个Person类,添加了一个serialVersionUID变量:
import java.io.Serializable;
public static class Person implements Serializable {
private static final long serialVersionUID = 1234L;
public String name = null;
public int age = 0;
}
Java对象的相关序列化API用serialVersionUID来判断反序列化的对象,在序列化的时候是否是被同一个版本的class序列化的(译注:这个变量是uuid,所以不会有重复值),因为它现在正试图去用这个类来反序列化。
假设一个Person对象被序列化到了磁盘,然后Person类有了一些改变。之后你尝试去反序列化这个Person对象。现在已经序列化的Person对象可能已经和最新版本的Person类不一样了。
一个类实现了Serializable接口后可以提供一个serialVersionUID的静态常量。如果这个类有比较大的改变,你同样也可以重新生成serialVersionUID值。
译者注:这里原文应该没有阐述清楚,上述的例子中,如果没有serialVersionUID,并且反序列化时候类发生了变化比如增加了字段,那么会抛出异常,如果有这个serialVersionUID,即使增加字段,反序列化时候Java也会认为这是同一对象,只不过新增加的字段会为null,因为被序列化的对象并没有此字段。
JDK和一些Java开发工具都包含了一些生成serialVersionUID的办法。
现在的对象Serialization
在现在(2015或更久以前),许多Java项目更多的使用不同的机制来序列化Java对象,而不是Java本身的序列化机制。例如,Java对象序列化到JSON,BSON或其他更优的二进制格式。这些有着非Java程序也可读的优点。例如,在web浏览器中运行的JavaScript可以自然的序列化和反序列化对象和JSON。
顺便说一下,这些其他的对象序列化直接一般不会需要你在Java类中去实现Serializable接口-它不会添加任何有用的信息。
更多关于Serialization的信息
对象的序列化本身就是一门学科。这个Java IO教程最多也就是关注一下stream或readers / writer。因此我不会在此来讨论一些序列化的具体细节。另外,对于Java对象序列化的只是网上已经有很多相关内容了。我将不会重复它,而是给你一个关于这个学科更深层次的连接:http://www.oracle.com/technetwork/articles/java/javaserial-1536170.html
Reader
在Java API中,Java Reader类(java.io.Reader)是所有Reader的基类。Reader和InputStream不同的地方在于它是基于字符流的而不是字节流。换句话说,Reader是用来读取文本的数据的,而InputStream是用来读取原始的字节的。
Unicode字符
现在,许多应用都使用UTF (UTF-8 或 UTF-16)格式来存储文本数据。UTF-8中一个或多个字节来表示一个字符。UTF-16编码中一个字符用两个字节来表示。因此在使用UTF编码时,文本数据中的一个字节并不一定代码一个字符。如果你只是通过InputStream来读取字节然后把字节转换成字符,那么并不一定会得到你想要的结果。
我们有Reader相关类来解决这个问题。它们可以把字节转换成字符。你需要告诉Reader你要以什么编码格式来读取数据,这会在实例化Reader时设置(实际上时在你实例化其子类的时候)。
用Reader读取字符
Reader的 read()方法会返回一个int值,包含下一个要读取的的字符值。如果方法返回 -1 则说明已经没有数据了。也就是说,-1作为int值,而不是-1作为字节或char值。这是一个不同的地方。
Reader子类
你更多的会用Reader的子类而不是直接用Reader。Java IO包含了许多Reader的子类。例如InputStreamReader,CharArrayReader,FileReader等许多其他的。更多的内容可以去看“Java IO 概览”的相关章节。
Reader 和 数据源
文件,字符数组,网络socket等数据是Reader有代表性的数据源,这个也是在“Java IO 概览”有详细的描述。
Writer
在Java API中,Java Writer类(java.io.Writer)是所有Writer的基类。Writer和OutputStream 不同的地方在于它是基于字符流的而不是字节流。换句话说,Writer是用来写文本的数据的,而OutputStream 是用来写原始的字节的。
Unicode字符
现在,许多应用都使用UTF (UTF-8 或 UTF-16)格式来存储文本数据。UTF-8中一个或多个字节来表示一个字符。UTF-16编码中一个字符用两个字节来表示。因此在使用UTF编码时,文本数据中的一个字节并不一定代码一个字符。要正确地编写utf-8或utf-16,您需要知道您想要存储文本的两种格式中的哪一种,您需要知道如何使用所选的格式正确地编码字符。
这就是Java Writer的方便之处。Java Writer的子类一般可以为你处理UTF-8 或 UTF-16编码,所以你不用担心这个。
Writer子类
你更多的会用Writer的子类而不是直接用Writer。Java IO包含了许多Writer的子类。例如OutputStreamWriter,CharArrayWriter,FileWriter等许多其他的。更多的内容可以去看“Java IO 概览”的相关章节。
Writers和目的地
Java Writer一般会将数据写到文件,字符数组,网络socket等。这个也是在“Java IO 概览”有详细的描述。