几个问题:
什么是java对象序列化?为什么要序列化?
如何序列化?
一、什么是对象序列化?为什么要序列化?
对象序列化:将java对象的状态保存成字节流,并可以在需要的时候再将对象从字节流中读出来
要序列化的原因:
1、在网络上传输的是字节流,java对象要想从网络的一端传到另外一端就需要序列化
2、对象需要持久化的时候 (想到了对象数据库,和这个有关?暂时没去了解)
二、java中如何实现序列化
1、自己实现序列化机制
通过实现Externalizable接口(暂不学习)
优势: 这种方法实现的序列化所需保存的信息比默认的少
2、默认序列化机制
这种方法的序列化保存的信息有:对象的类、类签名以及非瞬态(非transient,有关键字transient的属性将不会被序列化)和非静态字段的值(静态字段属于类的)。其他对象的引用(瞬态和静态字段除外)也会导致写入那些对象 (来自JDK帮助文档)
3、序列化算法
1)将对象相关的类的元数据输出
2)递归的输出超类描述直到不再有超类
3)开始从最顶层的超类输出实例对象
4)递归的输出子类的实例对象
总结一下就是:自下而上类描述,从上到下实例对象
4、最简单的实验
Person类(必须实现Serializable)
![](http://hi.csdn.net/attachment/201111/13/0_132118659520py.gif)
用winhex软件打开person.out
各字节的意义(参考java序列化规范)
AC ED:STREAM_MAGIC. 声明使用了序列化协议。
00 05:STREAM_VERSION 序列化协议版本
73:TC_OBJECT 声明这是一个新的对象
开始输出对象相关类的描述
72:TC_CLASSDESC 声明一个新class描述开始
00 0B:Class名字的长度 (11个字节)
73 65 72 69 2E 50 65 72 73 6F 6E 是类的名称(seri.Person 每个字节是类名称一个字符的ASCII码)
B7 11 2C DE 27 05 2D E1 SerialVersionUID,序列化ID,如果没有生产,算法随机生成一个8字节的ID(因为是long型的 所以是8个字节)
02:标记号 声明该对象支持序列化
00 02:该类包括域的个数 两个(name & age)
49:域类型 I Int
00 03:域名字的长度
61 67 65:域名字 age (ASCII码)
4C:域的类型 L Ljava/lang/String
00 04:域名字长度
6E 61 6D 65:域名字name
74:TC_String 代表一个新String,用String来引用对象 (Person p)
00 12:该String长度
4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B:JVM标准对象签名表示法
78:tc_endblockdata 对象块结束标志
70:tc_null 说明没有其他超类标志 (如果还有超类会继续输出超类的描述)
开始输出对象实例描述
00 00 00 19:25 age域的值
74:TC_String 代表一个新String,用String来引用name的
00 05:5个字节
73 74 65 76 65:steve name域的值
从person.out里重新得到Person对象
得到的结果是steve和25
三、值得关注的问题
1、序列化ID(Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据)
Person类有个警告是因为没有加private static final long serialVersionUID = -5255369957515645471L; (是想测试一下不定义的情况下与eclipse下随机生成的是否一样,结果它们是一样的),后来查了一下都是通过JDK bin目录下的serialver.exe工具产生的,可以直接用命令行运行,得到的结果也是一样的。(根据类名、接口名、成员方法及属性等来生成的哈希字段)
在person.out里看到的serialVersionUID是B7 11 2C DE 27 05 2D E1 转换成十进制就是 -5255369957515645471L
作用:Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
2、几种使用情况
1)类A没有父类,自己实现了Serializable接口
2)类A有父类B,且父类实现了Serializable接口
此时类A不需要实现Serializable接口(默认是可序列化的),如果A不想序列化,则在类A中加writeObject()和readObject()方法,在方法里抛出异常
3)类A有父类B,但父类没有实现Serializable接口
则必须在类A中显示实现writeObject()和readObject()方法来处理父类B的状态信息;
还有一点要特别注意,在父类B中一定要有一个无参的构造函数,这是因为在反序列化的过程中并不会使用声明为可序列化的类A的任何构造函数,而是会调用其没有声明为可序列化的父类B的无参构造函数。
3、序列化的高级使用
1)序列化 ID 的问题
2)静态变量序列化
4)对敏感字段加密
5)序列化存储规则
参考 (java序列化高级知识)