序列化深入理解

序列化使用与理解

关于序列化,你是否有思考过以下问题

  • 序列化之后,存了那些东西,是什么结构?
  • serialVersionUID 有什么用,没有会怎么样?
  • 字段增加减少后是否有兼容性问题?

本节 图解序列化的存储结构, 了解 serialVersionUID 的生成方式, 对序列化兼容性有一个更加全面的说明

基本使用

public class SerialModel implements Serializable {

    private String name;
    // 忽略 getter setter
}
        byte [] objData;
        // 使用了java7 提供的 try-with-resource 关闭流
        try(ByteArrayOutputStream byteArrayInputStream = 
        		new ByteArrayOutputStream();
		ObjectOutputStream objectInputStream =
				new ObjectOutputStream(byteArrayInputStream)){

            SerialModel serialModel = new SerialModel();
            serialModel.setName("Karen");
            objectInputStream.writeObject(serialModel);
            objData = byteArrayInputStream.toByteArray();
        }

这是一个简单的小例子,将对象SerialModel写入到ObjectOutputStream

图解序列化结构

String结构

@Test
    public void testSerialString() throws IOException {

        String name = "Karen Aya";
        try(ByteArrayOutputStream byteArrayInputStream = 
        				new ByteArrayOutputStream();
			ObjectOutputStream objectInputStream =
					new ObjectOutputStream(byteArrayInputStream)){

            objectInputStream.writeObject(name);
            byte [] result = byteArrayInputStream.toByteArray();

            FileOutputStream fileOutputStream = 
            	new FileOutputStream(new File("d:/stringSerial.txt"));
            fileOutputStream.write(result, 0, result.length);
            fileOutputStream.close();
        }
    }

文件内容如下:

00000000h: AC ED 00 05 74 00 09 4B 61 72 65 6E 20 41 79 61 ; ..t..Karen Aya

ObjectOutputStream 默认写出内容

String 序列化图
String

  1. 魔数: 0xaced
  2. 版本: 0x0005
  3. 类型: (Short String)0x74,(Long String) 0x7C
  4. 长度: 0x0009
  5. 内容: Karen Aya,4B 61 72 65 6E 20 41 79 61

基本结构非常简单,魔术和版本是由 ObjectOutputStream 对象创建时提供,与具体数据内容、类型无关
后面的类型、内容、长度 就是String类型序列化后的具体内容了

类结构

序列化实体类

public class SerialModel implements Serializable {
    private String name;
    // 省略 getter setter
}   

序列化测试方法

   @Test
    public void testDecimalSimple() throws IOException {

        byte [] objData;
        try(ByteArrayOutputStream byteArrayInputStream = 
        		new ByteArrayOutputStream();
            ObjectOutputStream objectInputStream =
            	new ObjectOutputStream(byteArrayInputStream)){

            SerialModel serialModel = new SerialModel();
            serialModel.setName("Karen");
            objectInputStream.writeObject(serialModel);
            objData = byteArrayInputStream.toByteArray();
        }

        FileOutputStream fileOutputStream = 
        			new FileOutputStream(new File("d:/buf.txt"));
        fileOutputStream.write(objData,0,objData.length);
        fileOutputStream.close();

    }

buf.txt 内容如下

偏移       16进制                                            字符串
00000000h: AC ED 00 05 73 72 00 13 63 6F 6D 2E 61 79 61 2E ; ..sr..com.aya.
00000010h: 53 65 72 69 61 6C 4D 6F 64 65 6C 09 9D B0 59 D6 ; SerialModel.澃Y?
00000020h: 38 AD 0C 02 00 01 4C 00 04 6E 61 6D 65 74 00 12 ; 8?...L..namet..
00000030h: 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E ; Ljava/lang/Strin
00000040h: 67 3B 78 70 74 00 05 4B 61 72 65 6E             ; g;xpt..Karen

这些二进制的内容图形表达如下,具体细节分析参见序列化类结构
类序列化内容

序列化id的生成与作用

serialVersionUID 由 ObjectStreamClass.lookup(clazz) 生成,若类中已经定义了 serialVersionUID 则会覆盖

影响 serialVersionUID 的内容:

  1. 类名
  2. 类描述符
  3. 类实现的接口
  4. 私有字段(排除static,transient)
  5. 类静态方法
  6. 类构造方法
  7. 非私有方法
    通过上述内容生成 SHA-1 值,做如下操作生成最终的serialVersionUID值
  long hash = ((sha[0] >>> 24) & 0xFF) |
	      ((sha[0] >>> 16) & 0xFF) << 8 |
	      ((sha[0] >>> 8) & 0xFF) << 16 |
	      ((sha[0] >>> 0) & 0xFF) << 24 |
	      ((sha[1] >>> 24) & 0xFF) << 32 |
	      ((sha[1] >>> 16) & 0xFF) << 40 |
	      ((sha[1] >>> 8) & 0xFF) << 48 |
	      ((sha[1] >>> 0) & 0xFF) << 56;

作者尝试根据官方文档的方式,做以下操作,并未生成出默认的 serialVersionUID 完全一致的值

  1. 将上述内容写入DataOutputStream
  2. 对内容进行SHA-1加密
  3. 将加密的内容转换成int数组,然后进行hash计算

尝试寻找 open-jdk 的源码也未找到如何生成与jdk一致的 serialVersionUID 值。

生成类的序列化id: ObjectStreamClass.lookup(SerialModel.class).getSerialVersionUID()

oracle官方文档:oralce生成序列化id介绍

兼容性

默认

枚举,动态代理,接口的 serialVersionUID = 0L;

不定义serialVersionUID

还是以 SerialModel 为例, 若类中不定义 serialVersionUID , 发生 包名,,方法,字段,构造函数改动时,完全不兼容。(仅方法内容改动不影响)

定义serialVersionUID

定义下列模型,并说明兼容情况

序列化 SerialModel 版本A 的内容到文件

  1. 版本B 兼容 版本A,不受增加字段影响
  2. 版本C 兼容 版本A,不受减少字段影响
  3. 版本D 不兼容 版本A,受 serialVersionUID 影响

SerialModel 版本A

public class SerialModel implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private String id;
}

SerialModel 版本B,删除字段

public class SerialModel implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer age;
    private String name;
    private String id;
}

SerialModel 版本C,新增字段

public class SerialModel implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
}

SerialModel 版本D 修改serialVersionUID

public class SerialModel implements Serializable {
    private static final long serialVersionUID = 2L;
    private String name;
}

最佳实践

当类的改动不向前兼容时,修改版本号,并附上注释

/**
* serialVersionUID=1L,第一版本
* serialVersionUID=2L,第二版本,改动name字段
* serialVersionUID=3L,第三版本,删除字段xx,新增字段
* serialVersionUID=3L,第四版本,新增字段yy,逻辑不冲突,向前兼容
*/
private static final long serialVersionUID = 3L;

serialVersionUID 可以由IDE自动生成,也可以写成具体的数值(最小值为1)

扩展

序列化类结构

类结构中序列化数据与结构的对应内容

魔数: 0xaced
版本: 0x5
类型:Object:0x73
类型描述符 
  类标识符号: 0x72
  全路径名: com.aya.SerialModel (String) 长度:`00 13`,内容:`63 6F 6D 2E 61 79 61 2E 53 65 72 69 61 6C 4D 6F 64 65 6C`
  序列化id(serialVersionUID) 自动生成的serialVersionUID `09 9D B0 59 D6 38 AD 0C`
  序列化标识位(byte): 2:SC_SERIALIZABLE (4:SC_EXTERNALIZABLE  2:SC_SERIALIZABLE 1:SC_WRITE_METHOD 16:SC_ENUM)
字段
  字段长度 (short)`00 01`
  遍历字段
     字段类型  (byte)  L: 字符串 `4C`
     字段名称  (String)  字段长度:`00 04` 字段内容:`6E 61 6D 65`
     字段类型全称 (String) Ljava/lang/String;  `74` 字符串标识,`00 12` 字符串长度,字符串内容`4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B`
序列化结束符 `78`
父类描述符  NULL:`70` ,(内容与类型描述符一致,本例的父类是Object,所以内心描述符写入NULL)
字段内容: `74` 字符串标识,`00 05` 字符串长度,字符串内容`4B 61 72 65 6E`


写入序列化数据
  基本类型直接写入,其他类型地柜调用序列化写入
  类型: TC_REFERENCE 0x71
  写入处理器: 0x7e0000+3
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值