1. 程式人生 > 程式設計 >那些有趣的程式碼(三)--勤儉持家的 ArrayList

那些有趣的程式碼(三)--勤儉持家的 ArrayList

上週在群裡有小盆友問 transient 關鍵字是幹什麼的。這篇文章就以此為契機介紹一下 transient 的作用,以及在 ArrayList 裡面的應用。

要了解 transient 我們先聊聊 Java 的序列化。

複習序列化

所謂序列化是指,把物件轉化為位元組流的一種機制。同理,反序列化指的就是把位元組流轉化為物件。

serializable

  • 對於 Java 物件來說,如果使用 JDK 的序列化實現。物件需要實現 java.io.Serializable 介面。
  • 可以使用 ObjectOutputStream()ObjectInputStream() 對物件進行序列化和反序列化。
  • 序列化的時候會呼叫 writeObject()
    方法,把物件轉換為位元組流。
  • 反序列化的時候會呼叫 readObject() 方法,把位元組流轉換為物件。
  • Java 在反序列化的時候會校驗位元組流中的 serialVersionUID 與物件的 serialVersionUID 時候一致。如果不一致就會丟擲 InvalidClassException 異常。官方強烈推薦為序列化的物件指定一個固定的 serialVersionUID。否則虛擬機器器會根據類的相關資訊通過一個摘要演演算法生成,所以當我們改變類的引數的時候虛擬機器器生成的 serialVersionUID 是會變化的。
  • transient 關鍵字修飾的變數 不會 被序列化為位元組流

複習ArrayList

1、ArrayList 是基於陣列實現的,是一個動態陣列,容量支援自動自動增長

2、ArrayList 執行緒不安全

3、ArrayList 實現了 Serializable,支援序列化

勤儉持家

上文我們說到 ArrayList 是基於陣列實現,我們看看原始碼:

    /**
     * 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 /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; 複製程式碼

有幾個重要的資訊:

  • ArraryList 是動態陣列,這個 elementData 就是儲存物件的資料。
  • 這個陣列居然使用了 transient 來修飾。
  • 陣列的長度等於 ArrayList 的容量。而不是 ArrayList 的元素數量。
  • size 是指的 ArrayList 中元素的數量,不是動態陣列的長度。
  • size 沒有被 transient 修飾,是可以被序列化的。

這,怎麼回事。ArrayList 儲存資料的陣列,居然不需要序列化?

black

莫慌,我們繼續往下看程式碼。上文我們說過,物件的序列化和反序列化是通過呼叫方法 writeObject() 和 readObject() 完成了,我們發現,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
                int capacity = calculateCapacity(elementData,size);
                SharedSecrets.getJavaOISAccess().checkArray(s,Object[].class,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();
                }
            }
        }
複製程式碼

注意,在 writeObject() 方法中,

      // Write out all elements in the proper order.
      for (int i=0; i<size; i++) {
           s.writeObject(elementData[i]);
      }
複製程式碼

按需序列化,用了幾個下標序列化幾個物件。

讀取的時候也是:

     for (int i=0; i<size; i++) {
          a[i] = s.readObject();
     }
複製程式碼

有幾個讀幾個。

總結一下:

  • transient 修飾的變數不會被序列化。
  • ArrayList 的底層陣列 elementDatatransient 修飾,不會直接被序列化。
  • 為了實現 ArrayList 元素的序列化,ArrayList 重寫了 writeObject()readObject() 方法。
  • 按需序列化陣列,只序列化存在的資料,而不是序列化整個 elementData 陣列。

用多少,序列化多少,真是勤儉持家的 ArrayList。

有趣的程式碼系列

那些有趣的程式碼(一)--有點萌的 Tomcat 的執行緒池

那些有趣的程式碼(二)--偏不聽父母話的 Tomcat 類載入器