转载请注明出处:https://www.cnblogs.com/zhizaixingzou/p/10023948.html

目录

 

1. 序列化

1.1. 序列化的使用

1.1.1. Java序列化机制

所谓序列化,乃是一种编码方式

 

Java序列化API为处理对象序列化提供了一个标准机制

1)需要被序列化的类Employee需要实现Serializable接口,该接口是个标记接口。

2)反序列化时,直接使用字节将Employee对象还原出来的。

3)反序列化时必须确保该读取程序的classpath中包含有Employee.class,哪怕读取程序中并未出现Employee类(读取得到的是Object类),否则会抛出ClassNotFoundException

4如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化(前提是也实现了Serializable接口),同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。

5被写对象的类型是String,或数组,或Enum枚举类型都会默认继承类java.lang.Enum,而该类实现了Serializable接口,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException

6serialVersionUID一致才能反序列化成功,反序列化端少了字段,反序列化正常,只是多序列化的字段不在最终对象里。反序列化端多了字段,反序列化也正常,只是多出的字段都使用对象类型的默认值。

7)被声明为transient的成员变量不会被序列化反序列化时用类型默认值

8)序列化时可以写入多个对象,反序列化时可以按对应的顺序读取这些对象。

9)写对象时,如果将它类型转换为父类,序列化时依旧还是保留本类,反序列化时依旧需要在classpath中有本类存在才行,否则抛出异常ClassNotFoundException

SerialTest st = new SerialTest();

oos.writeObject((parent)st);

10)如果序列化类的父类没有实现序列化接口,那么反序列化时对应的父类值由它的无参数构造函数决定。

11)序列化的是对象的状态,即它的成员变量才会保存,不会序列化静态变量。持久化的数据均为存在于堆中的数据,static类型的数据存在于方法区中,不能被持久化。

1.1.2. 可选的序列化定制:实现Serializable接口

如果我们想要更改存储数据的方式,比如说在对象中含有一些敏感信息,在存储获取它们之前我们要进行加密解密。或者,序列化类中实现transient修饰的成员变量的序列化:

1)它是通过反射实现的,也就是ObjectInputStream调用readObject的时候,会调用待序列化对象的对应私有方法。

2还有其他三个方法,可供我们定制自己的序列化反序列化过程:

private void readObjectNoData() throws ObjectStreamException

用于初始化反序列化对象,当发生一些情况导致反序列化对象不能获得数据时调用

 

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException

如果序列化类实现了该方法,则在序列化时会先调用writeReplace方法并使用它返回的对象替换当前对象写入流中该方法返回的对象,必须是可序列化的。一旦实现了writeReplace就不再需要writeObjectreadObject它们会被忽略

 

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException

首先调用默认的readObject得到对象,如果该方法存在则调用该方法,通过this访问到刚才反序列化得到的对象的内容并处理得到自己想要的任意一个对象可以是非原始类的并最为最终的实例返回。常用于单例和枚举值的恢复。

1.1.3. 强制的序列化定制:实现Externalizable接口

1Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由程序员去完成而非使用默认的序列化机制。

2)反序列化过程会调用类的无参构造器(须为public创建一个新对象然后再将被保存对象的字段的值分别填充到新对象中。

3)对应的两个方法内的对应的方法与writeObjectreadObject一样的,效率更高些。

1.2. 序列化后的字节序列解读

1)实现的序列化类如下:

2)对应的字节序列及解读如下:

    AC ED: STREAM_MAGIC. 声明使用了序列化协议.

    00 05: STREAM_VERSION. 序列化协议版本.

    0x73: TC_OBJECT. 声明这是一个新的对象.  

 

    0x72: TC_CLASSDESC. 声明这里开始一个新Class。

    00 0A: Class名字的长度.

    53 65 72 69 61 6c 54 65 73 74: SerialTest,Class类名.

    05 52 81 5A AC 66 02 F6: SerialVersionUID, 序列化ID,如果没有指定,

    则会由算法随机生成一个8byte的ID.

    0x02: 标记号. 该值声明该对象支持序列化。

    00 02: 该类所包含的域个数。

 

    0x49: 域类型. 49 代表"I", 也就是Int.

    00 07: 域名字的长度.

    76 65 72 73 69 6F 6E: version,域名字描述.

 

    0x4C: 域的类型.

    00 03: 域名字长度.

    63 6F 6E: 域名字描述,con

    0x74: TC_STRING. 代表一个new String.用String来引用对象。

    00 09: 该String长度.

    4C 63 6F 6E 74 61 69 6E 3B: Lcontain;, JVM的标准对象签名表示法.

    0x78: TC_ENDBLOCKDATA,对象数据块结束的标志

 

    0x72: TC_CLASSDESC. 声明这个是个新类.

    00 06: 类名长度.

    70 61 72 65 6E 74: parent,类名描述。

    0E DB D2 BD 85 EE 63 7A: SerialVersionUID, 序列化ID.

    0x02: 标记号. 该值声明该对象支持序列化.

    00 01: 类中域的个数.

 

    0x49: 域类型. 49 代表"I", 也就是Int.

    00 0D: 域名字长度.

    70 61 72 65 6E 74 56 65 72 73 69 6F 6E: parentVersion,域名字描述。

    0x78: TC_ENDBLOCKDATA,对象块结束的标志。

    0x70: TC_NULL, 说明没有其他超类的标志。.

 

    00 00 00 0A: 10, parentVersion域的值.

    00 00 00 42: 66, version域的值.

 

    0x73: TC_OBJECT, 声明这是一个新的对象.

    0x72: TC_CLASSDESC声明这里开始一个新Class.

    00 07: 类名的长度.

    63 6F 6E 74 61 69 6E: contain,类名描述.

    FC BB E6 0E FB CB 60 C7: SerialVersionUID, 序列化ID.

    0x02: Various flags. 标记号. 该值声明该对象支持序列化

    00 01: 类内的域个数。

 

    0x49: 域类型. 49 代表"I", 也就是Int..

    00 0E: 域名字长度.

    63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, 域名字描述.

    0x78: TC_ENDBLOCKDATA对象块结束的标志.

 

    0x70:TC_NULL,没有超类了。

 

    00 00 00 0B: 11, containVersion的值.

3)序列化字节序列里的内容总结:

首先会有序列化协议的标识和协议版本。

不同的对象会有标识分隔。

序列化类名及serialVersionUID、域名及其类型和值。

父类和祖先类、引用成员的信息。

1.3. 序列化的概念

Java序列化是指把Java对象转换为字节序列的过程Java反序列化是指把字节序列恢复为Java对象的过程。

 

序列化又称串行化。

1.4. 序列化的需求场景

1)通过序列化把Java对象转换为字节序列以便可以在网络上传送两进程间的消息,也可以在RMI时用到。

2Java对象的生命周期不会比JVM长,为了在JVM停止后继续用,需要将对象持久化。

3)对象的深度克隆。

1.5. 理解serialVersionUID

1.5.1. serialVersionUID的作用

Java的序列化机制是通过判断序列化类的serialVersionUID验证序列化类本身的版本一致性的。也就是说在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地序列化类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就抛出异常java.io.InvalidCastException

 

如果序列化端和反序列化端serialVersionUID不一致,那么会报类似下面的异常:

即抛出java.io.InvalidCastException异常,同时指明本地类的serialVersionUID和字节流里的serialVersionUID的具体值,可以看到不一致的情况。

1.5.2. serialVersionUID的指定

当实现java.io.Serializable接口的类(序列化类)没有显式地指定一个serialVersionUID的时候,Java序列化机制会根据编译的Class文件自动生成一个serialVersionUID,这种情况下,如果Class文件没有发生变化即使增加空格换行增加注释等等,就算再同一编译器编译多次,serialVersionUID也不会变化的。


但是,我们建议在序列化类中都应该显示地指定一个serialVersionUID,因为若非如此,JVM会基于Class文件,根据自己的算法生成一个,不同的JVM算法不同,生成的serialVersionUID也不同,导致同一个类在反序列化端的不可读,如最常见的就是客户端JVM生成的对象传到了服务端JVM就不能反序列化了。(不显示指定,那么字段新增或减少都会生成不同的serialVersionUID,相反,指定后增加或减少字段都不影响反序列化)

 

serialVersionUID有两种显示的生成方式:

一是默认的1L,比如:private static final long serialVersionUID = 1L

二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:

private static final  long   serialVersionUID = xxxxL

 

64位的哈希字段serialVersionUID的生成有两种方式

1利用JDKbin目录下的serialver.exe工具

比如对于Address.class,执行命令:

E:\workspace\target\classes>serialver Address

Address:    static final long serialVersionUID = -687991492884005033L;

该方法是根据类名、接口名、成员方法及属性等来生成哈希字段的。 

2)借助IntelliJ IDEA

先在设置窗口,勾选如下选项:

File->Settings->Editor->Inspections->Java->Serialization issues->Serializable class without ’serialVersionUID’

然后,在目标类上Alt+Enter

回车后自动生成:

private static final long serialVersionUID = -4942861438344653366L;

1.6. Kryo序列化框架

1.6.1. 下载

http://search.maven.org/#search%7Cga%7C1%7Ccom.esotericsoftware.kryo

1.6.2. 引入项目

1.7. FAQ

1.7.1. 如果父类可序列化,但你不需要继承类为可序列化

如果你不希望继承类为可序列化,那么你需要实现自己的 writeObject() readObject() 方法,并且需要抛出NotSerializableException 异常。

1.8. 参考资料

http://www.importnew.com/24490.html

http://www.cnblogs.com/redcreen/articles/1955307.html

http://swiftlet.net/archives/1268

http://blog.csdn.net/lirx_tech/article/details/51303966

posted on 2018-11-26 23:57  Firefly(萤火虫)  阅读(230)  评论(0编辑  收藏  举报