序列化——考虑使用自定义的序列化形式

如果没有先认真考虑默认的序列化形式是否合适,则不要贸然接受

接受默认的序列化是一个非常重要的决定,你需要从灵活性,性能和正确性多个维度考虑。一般来讲,只有当你自行设计的自定义序列化形式与默认基本相同时,才能接受默认的序列化。

默认的序列化形式按顺序保存所有的非static 非 transient 字段。

    实际上,有些字段属性应该是中间过程使用,或者并非实际有效的信息属性。比如 ,集合框架中的 modCount,它是用来检查是否发生了并发修改的。这个值并不代表集合中的信息,所以,其实是不需要保存的数据。

    有些值确实是信息,但并非是最简化的有效信息。比如 ArrayList 中,每次扩容都要创建一个更大的数组。而在实际使用的时候,整个数组并不是全部有意义,它只在 size 对应的下标之前有意义。也就是,一个List 可能内部数组实际长度是50,而已使用可能是15。如果把整个数组全部序列化,是很浪费的。所以序列化时候要考虑这个问题,只序列化有效的部分。

    再比如,对于 LinkedList ,内部用了 Node 封装了保存的元素这种情况下,默认的序列化就会把 Node 中的所有元素也全部序列化保存。要避免这种浪费,就应该自定义序列化,只保存元素信息和顺序。至于 Node 本身的信息,应该在反序列化的时候重新创建实例,并按顺序保存到LinkedList 中


如果一个对象的实际信息与需要的信息时一致的,可以考虑使用默认的序列化

即便确定了默认的序列化形式是合适的,通常还是建议提供一个readObject 方法来保证约束关系和安全性。

在上一节讨论过这个问题,有些属性是带有约束的。序列化是绕过构造器的创建实例方式。所以,需要在序列化的时候考虑约束关系。

过多消耗空间

大多数时候,内部的信息并不完全需要被保存,只要有部分相关的运行时信息,我们完全可以依赖这些数据还原。

比如对于一个List ,我们不需要序列化其内部的数组,或者 node 链,只需要知道内部的所有元素,就可以恢复这些数据。其内部很多 elementData size 等数据完全就是根据元素计算出来的

过多消耗时间

序列化的逻辑并不了解对象本身的逻辑,它只能沿着默认的途径去遍历。过多并不需要的数据也就意味着需要更多的遍历过程。

可能引起栈溢出

因为序列化和反序列化是一个方法调用。对于List 等,原来的添加是分批次添加的,也可能一个个添加。如果把它放在一个默认的序列化方法中,在一个方法中执行过多的遍历,可能引起栈溢出。

比如,自己构造一个简单的list 并使用默认的序列化形式,如果此list 中有几千上万的元素,就可能引起溢出问题。在实际开发中,这样的列表并不算很大。默认的序列化形式显然无法满足

注意:在反序列化的时候,需要考虑 transient 修饰的字段的恢复,使用transient修饰的字段在序列化的时候会被忽略,在反序列化的时候会成为默认值,但实际上这些值应该是根据当时实例的状态计算出来的。

比如LinkedList 中 size 字段,如果你不处理,因为它是int,就会被初始化为默认的 0,其实应该根据实际元素的数量,重新计算出 size。


无论是否使用默认的序列化,都需要注意同步问题

如果此对象的某些方法需要同步,则序列化时候也需要同步,比如 Vector 是一个同步的容器,它的序列化方法

private void writeObject(java.io.ObjectOutputStream s)
          throws java.io.IOException {
      final java.io.ObjectOutputStream.PutField fields = s.putFields();
      final Object[] data;
      synchronized (this) {
          fields.put("capacityIncrement", capacityIncrement);
          fields.put("elementCount", elementCount);
          data = elementData.clone();
      }
      fields.put("elementData", data);
      s.writeFields();
  } 

当然,readObject 不需要同步。因为在反序列化成功之前,对象还没有产生,不存在同步问题


强烈建议,无论是否默认的序列化方法,主动声明 serialVersionUID

避免因为 uid 问题导致的不兼容,当 uid不同时,反序列化会报异常失败

性能有所提升:如果没有声明 uid ,jvm 会在序列化时,自动计算出一个 uid

如果类发生了任何一点变化,可能uid 都会不同,导致反序列化时出错

所以,如果不声明 uid ,就会导致可能一个小小的无关的变动,都会导致序列化不兼容

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值