浅谈java序列化那些知识Word文件下载.docx
- 文档编号:18489133
- 上传时间:2022-12-17
- 格式:DOCX
- 页数:14
- 大小:21.59KB
浅谈java序列化那些知识Word文件下载.docx
《浅谈java序列化那些知识Word文件下载.docx》由会员分享,可在线阅读,更多相关《浅谈java序列化那些知识Word文件下载.docx(14页珍藏版)》请在冰豆网上搜索。
10)
thrownewError("
Beserious.Morethan10heads?
!
"
);
this.noOfHeads=noOfHeads;
synchronized(Humanoid.class){
totalHeads+=noOfHeads;
publicintgetHeadCount(){
returntotalHeads;
该类的一个子类如下:
*Humanoid的实现类,实现了序列化接口
importjava.io.*;
publicclassPersonextendsHumanoidimplementsjava.io.Serializable{
privateStringlastName;
privateStringfirstName;
privatetransientThreadworkerThread;
privatestaticintpopulation;
publicPerson(StringlastName,StringfirstName){
this.lastName=lastName;
this.firstName=firstName;
synchronized(Person.class){
population++;
publicStringtoString(){
return"
Person"
+firstName+"
"
+lastName;
staticsynchronizedpublicintgetPopulation(){
returnpopulation;
1.2对象的序列化及反序列化
上面的类Person类实现了Serializable接口,因此是可以序列化的。
我们如果要把一个可以序列化的对象序列化到文件里或者数据库里,需要下面的类的支持:
java.io.ObjectOutputStream
如何正确的使用Java序列化技术技术研究系列下面的代码负责完成Person类的序列化操作:
*Person的序列化类,通过该类把Person写入文件系统里。
publicclassWriteInstance{
publicstaticvoidmain(String[]args)throwsException{
if(args.length!
=1){
System.out.println("
usage:
javaWriteInstancefile"
System.exit(-1);
FileOutputStreamfos=newFileOutputStream(args[0]);
ObjectOutputStreamoos=newObjectOutputStream(fos);
Personp=newPerson("
gaoyanbing"
"
haiger"
oos.writeObject(p);
如果我们要序列化的类其实是不能序列化的,则对其进行序列化时会抛出下面的异常:
java.io.NotSerializableException
当我们把Person序列化到一个文件里以后,如果需要从文件中恢复Person这个对象,我们需要借助如下的类:
java.io.ObjectInputStream
从文件里把Person类反序列化的代码实现如下:
*Person的反序列化类,通过该类从文件系统中读出序列化的数据,并构造一*个Person对象。
publicclassReadInstance{
javaReadInstancefilename"
FileInputStreamfis=newFileInputStream(args[0]);
ObjectInputStreamois=newObjectInputStream(fis);
Objecto=ois.readObject();
readobject"
+o);
1.3序列化对类的处理原则
并不是一个实现了序列化接口的类的所有字段及属性都是可以序列化的。
我们分为以下几个部分来说明:
u如果该类有父类,则分两种情况来考虑,如果该父类已经实现了可序列化接口。
则其父类的相应字段及属性的处理和该类相同;
如果该类的父类没有实现可序列化接口,则该类的父类所有的字段属性将不会序列化。
u如果该类的某个属性标识为static类型的,则该属性不能序列化;
u如果该类的某个属性采用transient关键字标识,则该属性不能序列化;
需要注意的是,在我们标注一个类可以序列化的时候,其以下属性应该设置为transient来避免序列化:
u线程相关的属性;
u需要访问IO、本地资源、网络资源等的属性;
u没有实现可序列化接口的属性;
(注:
如果一个属性没有实现可序列化,而我们又没有将其用transient标识,则在对象序列化的时候,会抛出
java.io.NotSerializableException异常)。
1.4构造函数和序列化
对于父类的处理,如果父类没有实现序列化接口,则其必须有默认的构造函数(即没有参数的构造函数)。
为什么要这样规定呢?
我们来看实际的例子。
仍然采用上面的Humanoid和Person类。
我们在其构造函数里分别加上输出语句:
Human'
sdefaultconstructorisinvoked"
synchronized(Humanoid.class){
Person'
sconstructorisinvoked"
在命令行运行其序列化程序和反序列化程序的结果为:
如何正确的使用Java序列化技术技术研究系列可以看到,在从流中读出数据构造Person对象的时候,Person的父类Humanoid的默认构造函数被调用了。
当然,这点完全不用担心,如果你没有给父类一个默认构造函数,则编译的时候就会报错。
这里,我们把父类Humanoid做如下的修改:
publicclassHumanoidimplementsjava.io.Serializable{
publicHumanoid(){
我们把父类标记为可以序列化,再来看运行的结果:
技术研究系列可以看到,在反序列化的时候,如果父类也是可序列化的话,则其默认构造函数也不会调用。
这是为什么呢?
这是因为Java对序列化的对象进行反序列化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成,毕竟我们的可序列化的类可能有多个构造函数,如果我们的可序列化的类没有默认的构造函数,反序列化机制并不知道要调用哪个构造函数才是正确的。
1.5序列化带来的问题
我们可以看到上面的例子,在Person类里,其字段population很明显是想跟踪在一个JVM里Person类有多少实例,这个字段在其构造函数里完成赋值,当我们在同一个JVM里序列化Person并反序列化时,因为反序列化的时候Person的构造函数并没有被调用,所以这种机制并不能保证正确获取Person在一个JVM的实例个数,在后面的部分我们将要详细探讨这个问题及给出比较好的解决方案。
2控制序列化技术
2.1使用readObject和writeObject方法
由于我们对于对象的序列化是采用如下的类来实现具体的序列化过程:
java.io.ObjectOutputStream而该类主要是通过其writeObject方法来实现对象的序列化过程,改类同时也提供了一种机制来实现用户自定义writeObject的功能。
方法就是在我们的需要序列化的类里实现一如何正确的使用Java序列化技术技术研究系列个writeObject方法,这个方法在ObjectOutputStream序列化该对象的时候就会自动的回调它。
从而完成我们自定义的序列化功能。
同样的,反序列化的类也实现了同样的回调机制,我们通过扩展其readObject来实现自定义的反序列化机制。
通过这种灵活的回调机制就解决了上面提出的序列化带来的问题,针对上面的Person的问题,我们编写如下的readObject方法就可以彻底避免population计数不准确的问题:
privatevoidreadObject(ObjectInputStreamois)throwsIOException,ClassNotFoundException{
ois.defaultReadObject();
AdjustingpopulationinreadObject"
2.2序列化过程的类版本控制
本节讨论以下问题:
u在对象反序列化过程中如何寻找对象的类;
u如果序列化和反序列化两边的类不是同一个版本,如何控制;
2.2.1序列化类的寻找机制
在对象的反序列化过程中,是一定需要被反序列化的类能被ClassLoader找到的,否则在反序列化过程中就会抛出java.lang.ClassNotFoundException异常。
关于ClassLoader如何寻找类,这里就不多说了,可以参考我的另一篇讨论ClassLoader的文章《在非管理环境下如何实现热部署》。
我们这里只是关心该序列化对象对应的类是被哪个ClassLoader给Load的。
为此,我们修改上面的
*修改后的反序列化类
publicvoidreadPerson(Stringfilename){
try{
FileInputStreamfis=newFileInputStream(filename);
System.out.println(this.getClass().getClassLoader());
Personperson=(Person)o;
System.out.println(person.getClass().getClassLoader());
}catch(java.io.IOExceptionie){
ie.printStackTrace();
}catch(ClassNotFoundExceptionce){
ce.printStackTrace();
ReadInstancereadInstance=newReadInstance();
readInstance.readPerson(args[0]);
我们主要通过背景为黄色的两行代码查看其类加载器,运行结果如下:
由此可以看出,序列化类的类加载器正式其反序列化实现类的类加载器。
这样的话我们就可以通过使最新的Person类的版本发布为只有该反序列化器的ClassLoader可见。
而较旧的版本则不为该ClassLoader可见的方法来避免在反序列化过程中类的多重版本的问题。
当然,下面就类的版本问题我们还要做专门的探讨。
2.2.2序列化类多重版本的控制
如果在反序列化的JVM里出现了该类的不同时期的版本,那么反序列化机制是如何处理的呢?
为了避免这种问题,Java的序列化机制提供了一种指纹技术,不同的类带有不同版本的指纹信息,通过其指纹就可以辨别出当前JVM里的类是不是和将要反序列化后的对象对应的类是相同的版本。
该指纹实现为一个64bit的long类型。
通过安全的Hash算法(SHA-1)来将序列化的类的基本信息(包括类名称、类的编辑者、类的父接口及各种属性等信息)处理为该64bit的指纹。
我们可以通过JDK自带的命令serialver来打印某个可序列化类的指纹信息。
如下:
当我们的两边的类版本不一致的时候,反序列化就会报错:
解决之道:
从上面的输出可以看出,该指纹是通过如下的内部变量来提供的:
privatestaticfinallongserialVersionUID;
如果我们在类里提供对该属性的控制,就可以实现对类的序列化指纹的自定义控制。
为此,我们在Person类里定义该变量:
privatestaticfinallongserialVersionUID=6921661392987334380L;
则当我们修改了Person类,发布不同的版本到反序列化端的JVM,也不会有版本冲突的问题了。
需要注意的是,serialVersionUID的值是需要通过serialver命令来取得。
而不能自己随便设置,否则可能有重合的。
需要注意的是,手动设置serialVersionUID有时候会带来一些问题,比如我们可能对类做了关键性的更改。
引起两边类的版本产生实质性的不兼容。
为了避免这种失败,我们需要知道什么样的更改会引起实质性的不兼容,下面的表格列出了会引起实质性不兼容和可以忽略(兼容)的更改:
更改类型例子
兼容的更改
u添加属性(Addingfields)
u添加/删除类(adding/removingclasses)
u添加/删除writeObject/readObject方法(adding/removing
writeObject/readObject)
u添加序列化标志(addingSerializable)
u改变访问修改者(changingaccessmodifier)
u删除静态/不可序列化属性(removingstatic/transientfrom
afield)
不兼容的更改
u删除属性(Deletingfields)
u在一个继承或者实现层次里删除类(removingclassesina
hierarchy)
u添加静态/不可序列化字段(addingstatic/transienttoa
field)
u修改简单变量类型(changingtypeofaprimitive)
uswitchingbetweenSerializableorExternalizable
u删除序列化标志(removingSerializable/Externalizable)
u改变readObject/writeObject对默认属性值的控制(changing
whetherreadObject/writeObjecthandlesdefaultfielddata)
uaddingwriteReplaceorreadResolvethatproducesobjectsincompatiblewitholderversions
另外,从Java的序列化规范里并没有指出当我们对类做了实质性的不兼容修改后反序列化会有什么后果。
并不是所有的不兼容修改都会引起反序列化的失败。
比如,如果我们删除了一个属性,则在反序列化的时候,反序列化机制只是简单的将该属性的数据丢弃。
从JDK的参考里,我们可以得到一些不兼容的修改引起的后果如下表:
不兼容的修改引起的反序列化结果
删除属性
(Deletingafield)Silentlyignored
在一个继承或者实现层次里删除类
(Movingclassesininheritancehierarchy)
Exception
添加静态/不可序列化属性
(Addingstatic/transient)
Silentlyignored
修改基本属性类型
(Changingprimitivetype)
改变对默认属性值的使用
(Changinguseofdefaultfielddata)
在序列化和非序列化及内外部类之间切换
(SwitchingSerializableandExternalizable)
删除Serializable或者Externalizable标志
(RemovingSerializableorExternalizable)
返回不兼容的类
(Returningincompatibleclass)
Dependsonincompatibility
2.3显示的控制对属性的序列化过程
在默认的Java序列化机制里,有关对象属性到byte流里的属性的对应映射关系都是自动而透明的完成的。
在序列化的时候,对象的属性的名称默认作为byte流里的名称。
当该对象反序列化的时候,就是根据byte流里的名称来对应映射到新生成的对象的属性里去的。
举个例子来说。
在我们的一个Person对象序列化的时候,Person的一个属性firstName就作为byte流里该属性默认的名称。
当该Person对象反序列化的时候,序列化机制就把从byte流里得到的firstName的值赋值给新的Person实例里的名叫firstName的属性。
Java的序列化机制提供了相关的钩子函数给我们使用,通过这些钩子函数我们可以精确的控制上述的序列化及反序列化过程。
ObjectInputStream的内部类GetField提供了对把属性数据从流中取出来的控制,而ObjectOutputStream的内部
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 浅谈 java 序列 那些 知识