原文地址:Java中ObjectOutputStream的简单研究(转)作者:漫天飞雪
大家都知道在庞大而复杂的java IO体系中,java.io.ObjectOutputStream和java.io.ObjectInputStream这两个类可以方便的实现对象的序列化(Serialize)和反序列化(Deserialize)。但是其中的机制究竟如何,可能没有太多人关注,今天正好有人问我object output stream中的reset是干什么的,就借机会研究一下,就做了以下的简单的实验。
不过在实验以前先说说我的想法,我以前也很粗略的观察过object output stream序列化一个对象后生成的二进制传,大概就是类名加上一些非静态成员变量的数据,然后用自己的编码规则,也没往心里去。当时的感觉就是应用范围有限,因为这样序列化的对象只能用java自己的Object Input Stream来反序列化,所以就没有跨平台,跨语言的特性了。有时候为了将来能和其他平台通信,我甚至自己为某个类写过序列化和反序列化的方法(object-> byte[] 和byte[] -> object)。但是今天仔细看了一下,发现jdk果然比自己手写的那些简陋的代码要精巧的多。
实验的准备就是一个可以序列化的类:
class Abean implements Serializable {
private static final long serialVersionUID = -4903107312403938616L;
String aa;
String bb;
}
成员变量定义成字符型,是为了查看文件方便。需要注意的就是必须实现Serializable接口,否则直接exception了。
实验代码如下:
FileOutputStream fos = new FileOutputStream("ff");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Abean aa = new Abean();
aa.aa = "xx";
aa.bb = "yy";
for (int i = 0; i < 10; i++) {
oos.writeObject(aa);
oos.writeObject(aa);
// oos.reset();
}
fos.close();
FileInputStream fis = new FileInputStream("ff");
ObjectInputStream ois = new ObjectInputStream(fis);
for (int i = 0; i < 10; i++) {
Abean d = (Abean) ois.readObject();
System.out.printf("%st%sn", d.aa, d.bb);
}
测试代码也同样简单,通过控制其中一行来查看前后的区别。
之所以设计成写入10次同样的对象,就是测试前就猜想,reset文档中所谓的“清除写入的对象的信息”,可能就意味着如果不reset,后面写入的对象能利用之前写入对象的一些信息,来减小写入的数据。
果然,前后两次实验(注释和不注释其中那行),生成的文件的大小分别为:172字节和794字节。果然和预测一致,看来后面对象利用了前面已经写入的数据,大概后面再写入就加入了如“重复以前写入的某个对象”这样的信息,而不是再写入一次,所以文件大小减小了很多。
再仔细分析一下两次生成的文件,第二次就是写入了10次同样的信息,其中大概包括了前面说的类名,数据成员类型,每个数据成员的值。而第一次的文件,只写了一次这些信息,之后循环出现了了18次71 00 7E 00 02(16进制的数)这个串。
再来看下面一个实验,把原来循环语句改成
for (int i = 0; i < 10; i++) {
Abean aa = new Abean();
aa.aa = "xx";
aa.bb = "yy";
oos.writeObject(aa);
oos.writeObject(aa);
// oos.reset();
}
也就是每次都new一个对象出来,这样不reset时文件大小为271字节,每次reset时文件大小为794字节(和之前一样)。
再仔细看一眼两次不reset的文件,由172字节增加到271字节,一共增加了99个字节。第一个对象的信息结束时,两者是一样大的,而后面重复出现了9次一个21字节的串,也就是21*9 = 189,所以增加了90个字节。
当然以上实验都是基于感性的认识和分析。由于java虚拟机有不同的实现,所以这套机制必然有详细的文档说明,鉴于时间原因,就不再仔细研究了。
回到文章开头的那个问题,调用reset之后,会清除所有缓存的对象信息,再传输任何对象都会传输完整的一份内容。这显然会增加IO负担,但有些场景可能有它特殊的用处。
表:各个实验中生成文件大小的对比
实验 |
文件大小(字节) |
不reset,只new一次 |
172 |
每次rest, 只new一次 |
794 |
不reset,每次new |
271 |
每次reset,每次new |
794 |
总结一下,Object Output/Input Stream这套对象的序列化、反序列化机制确实有了不错的优化,在对象可能重复的情况下,能有效减低IO开销。但是对对象很少不重复的情况下,因为附带了类型信息,就很可能反而增加IO负担了。