您的位置:首页 > 编程语言 > Java开发

Java IO 经典教程 (中) (翻译自jenkov.com)

2017-12-10 20:04 447 查看
此系列文章翻译自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 概览”有详细的描述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: