一、java序列化详解
生成对象的二进制文件:
Student s1 = new Student("张三", 18);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/Users/zhangsan/student.txt"));
out.writeObject(s1);
out.close();
Student的定义:
static class Student implements Serializable{
private static final long serialVersionUID = 7606147849215550380L;
private String name;
private int age;
public Student(String name, int age){
this.name = name;
this.age = age;
}
}
生成后的二进制的文件:
image.png
二、解析序列化后的二进制文件
既然java序列化生成上面的二进制文件,那么它就能完整解析处理。也只有java才知道它序列化时的协议,按照这个协议,我们自己也能实现反序列化工具。下面开始解析这个二进制文件。
接下来按照顺序逐一解读
- 魔法数 ,(可在ObjectStreamConstants接口中找到)
- 序列化格式的版本号 (可在ObjectStreamConstants接口中找到)
- 接下来的 73 72 解读如下 (参见 协议文档)
73 代表接下来读取到的将是一个对象 (final static byte TC_OBJECT = (byte)0x73;)
72 代表该对象是一个对类的描述 (final static byte TC_CLASSDESC = (byte)0x72;) - 接下来的 00 33, 指代该类描述信息的长度, 经过转换计算, 51字节数据,内容正好是类com.example.demo.serializable.TransientTest.Student的完整类名,长度正好匹配
- 然后是69 8e77 2b75 9823 ac,这八位是用来验证该类是否被修改过的验证码,也就是serialVersionUID 字段
- 接下来就是 02, 该一个字节长度的标志信息代表了 序列化中标识类版本 ; 该数值也是可以在ObjectStreamConstants接口中找到. (final static byte SC_SERIALIZABLE = 0x02;)
- 继续往下就是 00 02 , 这两个字节长度的标志信息指代的是 该类型中字段的个数. 如这里所见, 正好对应了Student 中的两个字段.
接着往下就是对这三个字段的逐一解读了:
7.1 紧接着就是49,查询ASCII码得到49表示I,即Integer类型。
7.2 紧接着是00 03,表示该Integer类型字段名的长度有多少字节。表示有3个字节。
7.3 取出上面所说的3个字节61 6765,继续查询ASCII码61=a,67=g,65=e。就是Student定义的age字段。
7.4 继续解析字段名称。接下来1个字节 4c,查码表知道4c=L,L表示啥呢?java源码其实给了解释。如下:
switch (signature.charAt(0)) {
case 'Z': type = Boolean.TYPE; break;
case 'B': type = Byte.TYPE; break;
case 'C': type = Character.TYPE; break;
case 'S': type = Short.TYPE; break;
case 'I': type = Integer.TYPE; break;
case 'J': type = Long.TYPE; break;
case 'F': type = Float.TYPE; break;
case 'D': type = Double.TYPE; break;
case 'L':
case '[': type = Object.class; break;
default: throw new IllegalArgumentException("illegal signature");
}
L没有定义,其他都有定义。说明L表示用户自定义的类型。
7.4 然后紧接着的2个字节00 04,表示该字段名长度为4字节。向后取4个字节出来6e 61 6d 65,继续查码表得到name。由于L类型需要获取类型信息,所以后面必须跟着类型信息。如下:
for (int i = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
out.writeByte(f.getTypeCode());
out.writeUTF(f.getName());
if (!f.isPrimitive()) {
out.writeTypeString(f.getTypeString());
}
}
如果f不是基本类型,那么就必须writeTypeString。里面是已74为标志开始的。4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b表示:java.lang.String;然后已78 70(序列化约定的)标志结束。
- 然后开始内容解析,由于第一个字段是age,类型是int,表示4个字节。获取7870后面的4个字节:0000 0012,十进制=18。即age=18
- 然后获取74表示结束标志。后面紧跟的2个字节表示name的长度。00 06,表示长度为6个字节。
- 查询0006,后面正好6个字节,然后全部结束。e5 bc a0 e4 b8 89,可以e5开头,查询UTF-8,可知e5 bc a0表示:张。e4 b8 89表示:三。
到此整个java序列化的二进制文件,全部分析完成。
网友评论