您的位置:首页 > 其它

序列化与ArrayList 的elementData的修饰关键字transient

2018-08-29 15:53 477 查看

  transient用来表示一个域不是该对象序行化的一部分,当一个对象被序行化的时候,transient修饰的变量不会被序列化

  ArrayList的动态数组elementData被transient  修饰的  那么岂不是反序列化后的ArrayList丢失了原先的元素, 其实不然.  ArrayList在序列化的时候会调用writeObject,反序列化时调用readObject 也就是自定义序列化

  • 为什么要自定义序列化? 因为ArrayList数组elementData中有未使用的空间 ,如果没有使用的空间也序列化,势必会影响性能.
  • 基本概念
  •     序列化:将一个对象转换成一串二进制表示的字节数组,通过保存或转移这些字节数据来达到持久化的目的。
        反序列化:将字节数组重新构造成对象。

    • 默认序列化

      序列化只需要实现java.io.Serializable接口就可以了。序列化的时候有一个serialVersionUID参数,Java序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。 在进行反序列化,Java虚拟机会把传过来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较, 如果相同就认为是一致的实体类,可以进行反序列化,否则Java虚拟机会拒绝对这个实体类进行反序列化并抛出异常。

    • serialVersionUID有两 种生成方式:

        1、默认的1L

        2、根据类名、接口名、成员方法以及属性等来生成一个64位的Hash字段

      如果实现 java.io.Serializable接口的实体类没有显式定义一个名为serialVersionUID、类型为long的变量时,Java序列化 机制会根据编译的.class文件自动生成一个serialVersionUID,如果.class文件没有变化,那么就算编译再多 次,serialVersionUID也不会变化。换言之,Java为用户定义了默认的序列化、反序列化方法,其实就是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。

    • 从以上对于序列化后的二进制文件的解析,我们可以得出以下几个关键的结论:

        1、序列化之后保存的是类的信息
        2、被声明为transient的属性不会被序列化,这就是transient关键字的作用
        3、被声明为static的属性不会被序列化,这个问题可以这么理解,序列化保存的是对象的状态,但是static修饰的变量是属于类的而不是属于变量的,因此序列化的时候不会序列化它

    • 手动指定序列化过程:

        Java并不强求用户非要使用默认的序列化方式,用户也可以按照自己的喜好自己指定自己想要的序列化方式----只要你自己能保证序列化前后能得到想要的数据就好了。手动指定序列化方式的规则是:

        进行序列化、反序列化时,虚拟机会首先试图调用对象里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有这 样的方法,那么默认调用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的 defaultReadObject方法。换言之,利用自定义的writeObject方法和readObject方法,用户可以自己控制序列化和反序列 化的过程。这是非常有用的。

    • 比如:

        ArrayList的 elementData、HashMap的table

    /**
    * The array buffer into which the elements of the ArrayList are stored.
    * The capacity of the ArrayList is the length of this array buffer. Any
    * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    * will be expanded to DEFAULT_CAPACITY when the first element is added.
    */
    transient Object[] elementData; // non-private to simplify nested class access

        transient  当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中

        显然诸如 ArrayList在初始化的时候 就有空间了, 我们在操作list的时候 会存在未使用的空间,如果在序列化的时候把未使用的也序列化就不合理了

        所以ArrayList有writeObject和readObject方法自定义了序列化与反序列化: 

    private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();
    
    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);
    
    // Write out all elements in the proper order.
    //只序列化了被使用的数据
    for (int i=0; i<size; i++) {
    s.writeObject(elementData[i]);
    }
    
    if (modCount != expectedModCount) {
    throw new ConcurrentModificationException();
    }
    }
    
    private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;
    
    // Read in size, and any hidden stuff
    s.defaultReadObject();
    
    // Read in capacity
    s.readInt(); // ignored
    
    if (size > 0) {
    // be like clone(), allocate array based upon size not capacity
    ensureCapacityInternal(size);
    
    Object[] a = elementData;
    // Read in all elements in the proper order.
    for (int i=0; i<size; i++) {
    a[i] = s.readObject();
    }
    }
    }

     

    • 序列化并不安全,因此有些场景下我们需要对一些敏感字段进行加密再序列化
    • 复杂序列化情况总结

        虽然Java的序列化能够保证对象状态的持久保存,但是遇到一些对象结构复杂的情况还是比较难处理的,最后对一些复杂的对象情况作一个总结:
          1、当父类继承Serializable接口时,所有子类都可以被序列化
          2、子类实现了Serializable接口,父类没有,父类中的属性不能序列化(不报错,数据丢失),但是在子类中属性仍能正确序列化
          3、如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错
          4、反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错
          5、反序列化时,如果serialVersionUID被修改,则反序列化时会失败

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: