Java的ArrayList中,定义了一个数组elementData用来装载对象的,具体定义以下:java
/** * 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又是可序行化的类,elementData是ArrayList具体存放元素的成员,用transient来修饰elementData,岂不是反序列化后的ArrayList丢失了原先的元素?
其实玄机在于ArrayList中的两个方法:数组
/** * Save the state of the <tt>ArrayList</tt> instance to a stream (that * is, serialize it). * * @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) in the proper order. */ 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(); } }
/** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */ 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(); } } }
ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream;反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。
为何不直接用elementData来序列化,而采用上诉的方式来实现序列化呢?缘由在于elementData是一个缓存数组,它一般会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就能够保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。缓存
transient分析
注意到的是ArrayList和LinkedList中的一些变量被transient关键字修饰。如ArrayList中的elementData数组,LinkedList中指向头结点和尾结点的指针等。下面解释一下transient关键字的做用:this
java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我不想用serialization机制来保存它。为了在一个特定对象的域上关闭serialization,能够在这个域前加上关键字transient。transient是一个关键字,用来表示一个于不是该对象串行化的一部分。当一个对象被串行化的时候,被transient关键字修饰的变量的值不包括在串行化的表示中,非transient型的变量是被包括进去的。指针
那么既然用于保存数据的变量都被transient修饰,ArrayList和LinkedList还能不能被序列化呢?code
答案是能够的。对于ArrayList来讲,若是不把elementData申明为transient类型,那么序列化的时候里面的数据都会被序列化,可是elementData这个数组很大程序是存在空值的状况(即size《length),这时若是序列化就会致使磁盘空间被浪费。为了解决这个问题,ArrayList将elementData申明为transient,本身重写了writeObject方法,保证只序列化elementData中有数据的那部分,ArrayList中的writeObject方法以下:对象
(见上文)ci
注意的是这里面也用到了modCount,若是不一致说明这段时间集合发生了改变,抛出异常。element
LinkedList中的序列化和ArrayList中的序列化还不同,LinkedList中不会存在说有多余的元素这种说法,可是因为LinkedList中申明为transient类型的数据均可以不用进行序列化,因此进行申明,好比分别指向头结点和尾结点的first和last指针。it