如何领略Java工具的序列化
副标题#e#
关于Java序列化的文章早已是汗牛充栋了,本文是对我小我私家过往进修,领略及应用Java序列化的一个总结。此文内容涉及Java序列化的根基道理,以及多种要领对序列化形式举办定制。在撰写本文时,既参考了Thinking in Java, Effective Java,JavaWorld,developerWorks中的相关文章和其它网络资料,也插手了本身的实践履历与领略,文、码并茂,但愿对各人有所辅佐。(2012.02.14最后更新)
一、什么是Java工具序列化
Java平台答允我们在内存中建设可复用的Java工具,但一般环境下,只有当JVM处于运行时,这些工具才大概存在,即,这些工具的生命周期不会比JVM的生命周期更长。但在现实应用中,就大概要求在JVM遏制运行之后可以或许生存(耐久化)指定的工具,并在未来从头读取被生存的工具。Java工具序列化就可以或许辅佐我们实现该成果。
利用Java工具序列化,在生存工具时,会把其状态生存为一组字节,在将来,再将这些字节组装成工具。必需留意地是,工具序列化生存的是工具的”状态”,即它的成员变量。由此可知,工具序列化不会存眷类中的静态变量。
除了在耐久化工具时会用到工具序列化之外,当利用RMI(长途要领挪用),或在网络中通报工具时,城市用到工具序列化。Java序列化API为处理惩罚工具序列化提供了一个尺度机制,该API简朴易用,在本文的后续章节中将会连续讲到。
二、简朴示例
在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。此处将建设一个可序列化的类Person,本文中的所有示例将环绕着该类或其修改版。
Gender类,是一个列举范例,暗示性别
Gender { MALE, FEMALE }
假如熟悉Java列举范例的话,应该知道每个列举范例城市默认担任类java.lang.Enum,而该类实现了Serializable接口,所以列举范例工具都是默承认以被序列化的。
Person类,实现了Serializable接口,它包括三个字段:name,String范例;age,Integer范例;gender,Gender范例。别的,还重写该类的toString()要领,以利便打印Person实例中的内容。
{ String name = ; Integer age = ; Gender gender = ; () { System.out.println(); } (String name, Integer age, Gender gender) { System.out.println(); .name = name; .age = age; .gender = gender; } String () { name; } (String name) { .name = name; } Integer () { age; } (Integer age) { .age = age; } Gender () { gender; } (Gender gender) { .gender = gender; } String () { + name + + age + + gender + ; } }
SimpleSerial,是一个简朴的序列化措施,它先将一个Person工具生存到文件person.out中,然后再从该文件中读出被存储的Person工具,并打印该工具。
SimpleSerial { (String[] args) throws Exception { File file = File(); ObjectOutputStream oout = ObjectOutputStream( FileOutputStream(file)); Person person = Person(, , Gender.MALE); oout.writeObject(person); oout.close(); ObjectInputStream oin = ObjectInputStream( FileInputStream(file)); Object newPerson = oin.readObject(); oin.close(); System..println(newPerson); } }
上述措施的输出的功效为:
arg [, 31, ]
此时必需留意的是,当从头读取被生存的Person工具时,并没有挪用Person的任何结构器,看起来就像是直接利用字节将Person工具还原出来的。
当Person工具被生存到person.out文件中之后,我们可以在其它处所去读取该文件以还原工具,但必需确保该读取措施CLASSPATH中包括有Person.class(哪怕在读取Person工具时并没有显示地利用Person类,如上例所示),不然会抛出ClassNotFoundException。
三、Serializable的浸染
为什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,利用ObjectOutputStream来耐久化工具,在该类中有如下代码:
(Object obj, unshared) IOException { (obj String) { writeString((String) obj, unshared); } (cl.isArray()) { writeArray(obj, desc, unshared); } (obj Enum) { writeEnum((Enum) obj, desc, unshared); } (obj Serializable) { writeOrdinaryObject(obj, desc, unshared); } { (extendedDebugInfo) { NotSerializableException(cl.getName() + + debugInfoStack.toString()); } { NotSerializableException(cl.getName()); } } }
#p#分页标题#e#
从上述代码可知,假如被写工具的范例是String,或数组,或Enum,或Serializable,那么就可以对该工具举办序列化,不然将抛出NotSerializableException。
四、默认序列化机制
假如仅仅只是让某个类实现Serializable接口,而没有其它任那里理惩罚的话,则就是利用默认序列化机制。利用默认机制,在序列化工具时,不只会序列化当前工具自己,还会对该工具引用的其它工具也举办序列化,同样地,这些其它工具引用的别的工具也将被序列化,以此类推。所以,假如一个工具包括的成员变量是容器类工具,而这些容器所含有的元素也是容器类工具,那么这个序列化的进程就会较巨大,开销也较大。
五、影响序列化
在现实应用中,有些时候不能利用默认序列化机制。好比,但愿在序列化进程中忽略掉敏感数据,可能简化序列化进程。下面将先容若干影响序列化的要领。
#p#副标题#e#
1、transient要害字
当某个字段被声明为transient后,默认序列化机制就会忽略该字段。此处将Person类中的age字段声明为transient,如下所示,
{ Integer age = ; }
再执行SimpleSerial应用措施,会有如下输出:
arg [, , ]
可见,age字段未被序列化。
2、writeObject()要领与readObject()要领
对付上述已被声明为transitive的字段age,除了将transitive要害字去掉之外,是否尚有其它要领能使它再次可被序列化?要领之一就是在Person类中添加两个要领:writeObject()与readObject(),如下所示:
{ Integer age = ; (ObjectOutputStream out) IOException { out.defaultWriteObject(); out.writeInt(age); } (ObjectInputStream in) IOException, ClassNotFoundException { in.defaultReadObject(); age = in.readInt(); } }
在writeObject()要领中会先挪用ObjectOutputStream中的defaultWriteObject()要领,该要了解执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再挪用writeInt()要领显示地将age字段写入到ObjectOutputStream中。readObject()的浸染则是针对工具的读取,其道理与writeObject()要领沟通。
再次执行SimpleSerial应用措施,则又会有如下输出:
arg [, 31, ]
必需留意地是,writeObject()与readObject()都是private要领,那么它们是如何被挪用的呢?毫无疑问,是利用反射。详情可见ObjectOutputStream中的writeSerialData要领,以及ObjectInputStream中的readSerialData要领。
3、Externalizable接口
无论是利用transient要害字,照旧利用writeObject()和readObject()要领,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口–Externalizable,利用该接口之后,之前基于Serializable接口的序列化机制就将失效。此时将Person类修改成如下,
{ String name = ; Integer age = ; Gender gender = ; () { System.out.println(); } (String name, Integer age, Gender gender) { System.out.println(); .name = name; .age = age; .gender = gender; } (ObjectOutputStream out) IOException { out.defaultWriteObject(); out.writeInt(age); } (ObjectInputStream in) IOException, ClassNotFoundException { in.defaultReadObject(); age = in.readInt(); } (ObjectOutput out) IOException { } (ObjectInput in) IOException, ClassNotFoundException { } }
此时再执行SimpleSerial措施之后会获得如下功效:
arg - [, , ]
从该功效,一方面可以看出Person工具中任何一个字段都没有被序列化。另一方面,假如细心的话,还可以发明这此序次列化进程挪用了Person类的无参结构器。
Externalizable担任于Serializable,当利用该接口时,序列化的细节需要由措施员去完成。如上所示的代码,由于writeExternal()与readExternal()要领未作任那里理惩罚,那么该序列化行为将不会生存/读取任何一个字段。这也就是为什么输出功效中所有字段的值均为空。
别的,若利用Externalizable举办序列化,当读取工具时,会挪用被序列化类的无参结构器去建设一个新的工具,然后再将被生存工具的字段的值别离填充到新工具中。这就是为什么在此序次列化进程中Person类的无参结构器会被挪用。由于这个原因,实现Externalizable接口的类必需要提供一个无参的结构器,且它的会见权限为public。
#p#分页标题#e#
对上述Person类作进一步的修改,使其可以或许对name与age字段举办序列化,但要忽略掉gender字段,如下代码所示:
{ String name = ; Integer age = ; Gender gender = ; () { System.out.println(); } (String name, Integer age, Gender gender) { System.out.println(); .name = name; .age = age; .gender = gender; } (ObjectOutputStream out) IOException { out.defaultWriteObject(); out.writeInt(age); } (ObjectInputStream in) IOException, ClassNotFoundException { in.defaultReadObject(); age = in.readInt(); } (ObjectOutput out) IOException { out.writeObject(name); out.writeInt(age); } (ObjectInput in) IOException, ClassNotFoundException { name = (String) in.readObject(); age = in.readInt(); } }
执行SimpleSerial之后会有如下功效:
arg - [, 31, ]
4、readResolve()要领
当我们利用Singleton模式时,应该是期望某个类的实例应该是独一的,但假如该类是可序列化的,那么环境大概会略有差异。此时对第2节利用的Person类举办修改,使其实现Singleton模式,如下所示:
{ { Person instatnce = Person(, , Gender.MALE); } Person () { InstanceHolder.instatnce; } String name = ; Integer age = ; Gender gender = ; () { System.out.println(); } (String name, Integer age, Gender gender) { System.out.println(); .name = name; .age = age; .gender = gender; } }
同时要修改SimpleSerial应用,使得可以或许生存/获取上述单例工具,并举办工具相等性较量,如下代码所示:
SimpleSerial { (String[] args) throws Exception { File file = File(); ObjectOutputStream oout = ObjectOutputStream( FileOutputStream(file)); //URL:http://www.bianceng.cn/Programming/Java/201608/50354.htm oout.writeObject(Person.getInstance()); oout.close(); ObjectInputStream oin = ObjectInputStream( FileInputStream(file)); Object newPerson = oin.readObject(); oin.close(); System..println(newPerson); System..println(Person.getInstance() == newPerson); } }
执行上述应用措施后会获得如下功效:
arg [, 31, ]
值得留意的是,从文件person.out中获取的Person工具与Person类中的单例工具并不相等。为了能在序列化进程仍能保持单例的特性,可以在Person类中添加一个readResolve()要领,在该要领中直接返回Person的单例工具,如下所示:
{ { Person instatnce = Person(, , Gender.MALE); } Person () { InstanceHolder.instatnce; } String name = ; Integer age = ; Gender gender = ; () { System.out.println(); } (String name, Integer age, Gender gender) { System.out.println(); .name = name; .age = age; .gender = gender; } Object () ObjectStreamException { InstanceHolder.instatnce; } }
再次执行本节的SimpleSerial应用后将有如下输出:
arg [, 31, ]
无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取工具时,readResolve()要领城市被挪用到。实际上就是用readResolve()中返回的工具直接替换在反序列化进程中建设的工具,而被建设的工具则会被垃圾接纳掉。
戴德:
领略Java工具序列化:http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html