java序列化算法揭秘

序列化是将对象保存为字节序列的过程,反序列化是将字节序列转换为对象的过程。Java Serialization API为开发者提供了一套标准机制来处理对象序列化。本文你将看到如何序列化一个对象以及为什么对象的序列化在有的情况下是必须的。你将会学习到java的序列化算法以及一个揭示序列化对象格式的例子。阅读完本篇内容后,你讲会对java的序列化算法如何工作以及一个实体对象在底层如何被序列化有一个深入的认识。

为什么需要序列化

如今,传统的企业应用将不同的组件分布在不同的系统和网络中。对java而言,一切事物都是对象,如果两个组件之间如果想要进行通信,那么它们之间必须要有数据转换机制。实现这一过程的一种方法是定义一种协议来传输对象,这意味着接收端需要知道发送端使用的协议来重建对象,以此来保证两组间之间的通讯不被第三方窃取。

图1展示了数据如何从发送端传输到接收端的过程。


如何序列化一个对象

为了序列化一个对象,你必须保证对象实现了java.io.Serializable接口,就像代码1展示的一样。

import java.io.Serializable;

class TestSerial implements Serializable {
    public byte version = 100;
    public byte count = 0;
}

现在你可以进行序列化了,接下来要做的就是利用java.io.ObjectOutputStream类中的writeObject()方法来序列化对象,如代码2所示。
public static void main(String args[]) throws IOException {
    FileOutputStream fos = new FileOutputStream("temp.out");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    TestSerial ts = new TestSerial();
    oos.writeObject(ts);
    oos.flush();
    oos.close();
}
代码2中将对象TestSerial的状态存储在文件temp.out中。
为了将文件中的数据反序列化为对象,你应该实现代码3中的代码。
public static void main(String args[]) throws IOException {
    FileInputStream fis = new FileInputStream("temp.out");
    ObjectInputStream oin = new ObjectInputStream(fis);
    TestSerial ts = (TestSerial) oin.readObject();
    System.out.println("version="+ts.version);
}
在代码3中,对象的反序列化发生在oin.readObject()方法调用的时候。这个方法将文件中的字节数据转换为一个对象。由于readObject()可以反序列化任意对象,所以调用时需要进行强制类型转换。
执行上面的代码会在控制台中打印出version=100。

序列化对象的存储格式

序列化对象在文件中的格式是什么呢?  例如上述序列化的TestSerial对象在文件中的格式(16进制表示)如代码4所示。
AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05
63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78
70 00 64
TestSerial对象包含两个byte变量,所以出去对象头部,对象大小是2字节。但是序列化后的对象列表4所示共有51字节,多出来的字节到底代表着什么?下面将详细讲解。

java序列化算法

现在你将详细了解到序列胡对象算法。通常情况下序列胡算法将遵循以下四条准则。
(1)算法会将关联实例的类的元数据写入文件。
(2)算法会递归地将类的父类写入文件,直到遇到Object类为止。
(3)类的元数据写完之后,算法便从最顶端的父类开始写入类实例的真实数据。
(4)算法将递归地将类实例的数据写入文件。
我们将写一个例子来包含上述所有的规则,新的对象如代码5所示。
class parent implements Serializable {
    int parentVersion = 10;
}

class contain implements Serializable{
    int containVersion = 11;
}
public class SerialTest extends parent implements Serializable {
    int version = 66;
    contain con = new contain();

    public int getVersion() {
        return version;
    }
    public static void main(String args[]) throws IOException {
        FileOutputStream fos = new FileOutputStream("temp.out");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        SerialTest st = new SerialTest();
        oos.writeObject(st);
        oos.flush();
        oos.close();
    }
}
本例中SerialTest继承了父类parent,而且成员变量中包含了对象contain。该对象的序列化文件内容如代码6所示。
AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 05 52 81 5A AC 66 02 F6 02 00 02 49 00 07
76 65 72 73 69 6F 6E 4C 00 03 63 6F 6E 74 00 09
4C 63 6F 6E 74 61 69 6E 3B 78 72 00 06 70 61 72
65 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 01 49 00
0D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 70
00 00 00 0A 00 00 00 42 73 72 00 07 63 6F 6E 74
61 69 6E FC BB E6 0E FB CB 60 C7 02 00 01 49 00
0E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 78
70 00 00 00 0B

对象的序列化过程如图2所示。


现在我们将逐行解读序列化对象存储在文件中的内容:
  •     AC ED:STREAM_MAGIC.    序列化协议标识
  •     00 05:STREAM_VERSION.    流版本号
  •     0x73:TC_OBJECT.    表示这是一个新对象  
第一步算法将描述类的实例,本例中是对象SerialTest。
  •     0x72: TC_CLASSDESC.    表示一个新对象
  •     00 0A: 类名长度
  •     53 65 72 69 61 6c 54 65 73 74:SerialTest(对应ASC码16进制),类名
  •     05 52 81 5A AC 66 02 F6: 序列化版本号
  •     0x02: 表示该对象支持序列化
  •     00 02:  表示类中域的个数(本例中SerialTest类有两个域version和con)
接下来将会写域int version = 66;。
  •     0x49:  域类型码,49表示"I",代表int类型
  •     00 07: 域名长度
  •     76 65 72 73 69 6F 6E: version,域的名称
接下来算法将会写域contain con = new contain();。由于con是一个对象,只会写入规范的JVM对象摘要。
  •     0x74: TC_STRING.    表示一个新字符串
  •     00 09: 表示字符串长度
  •     4C 63 6F 6E 74 61 69 6E 3B: Lcontain;,标准JVM摘要
  •     0x78: TC_ENDBLOCKDATA,表示该对象可选的数据块末端
下一步算法会将SerialTest类的父类parent写入文件。
  •     0x72: TC_CLASSDESC.    表示一个新对象
  •     00 06: 类名长度
  •     70 61 72 65 6E 74:  parent,类名
  •     0E DB D2 BD 85 EE 63 7A:  序列化版本号
  •     0x02: 表示该对象支持序列化
  •     00 01:  表示类中域的个数(本例中parent类有一个域parentVersion)
下一步算法将parent类的域写入。parent类只有一个域int parentVersion = 100;。
  •     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,类SerialTest中实例变量version的值
接下来的数据比较有趣,算法将实例变量con对应的类信息写入了文件
  •     0x73:TC_OBJECT.    表示这是一个新对象
  •     0x72: TC_CLASSDESC.    表示一个新对象
  •     00 07: 类名长度
  •     63 6F 6E 74 61 69 6E: contain,类名
  •     FC BB E6 0E FB CB 60 C7: 序列化版本号
  •     0x02: 表示该对象支持序列化
  •     00 01:  表示类中域的个数
接下来算法将会写类中的域int containVersion = 11;。
  •     0x49:  域类型码,49表示"I",代表int类型
  •     00 0E: 域名长度
  •     63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion,域名
  •     0x78: TC_ENDBLOCKDATA,表示该对象可选的数据块末端
最后算法将会检查类是否存在父类,如果有父类,则继续写父类的信息;如果没有父类,则写入TC_BULL。
  •     0x70: TC_NULL,表示向上已经没有父类
最后算法将写入类的实例变量对应的数值。
  •     00 00 00 0B: 11,类Contain中实例变量containVersion的值
以上就是java序列化算法的全内容。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值