集合詳解(二)----ArrayList源代碼剖析(JDK1.7)

ArrayList


    ArrayList是List類的一個典型的實現,是基於數組實現的List類,因此,ArrayList封裝了一個動態的、可變長度的Object[]數組。ArrayList是通過initialCapacity參數來設置數組長度的,當向ArrayList添加的數據超出了ArrayList的長度之後,initialCapacity會自動增加。

    

私有屬性

    ArrayList定義了兩個私有屬性:

//elementData存儲ArrayList內的元素,size表示它包含的元素的數量。
private transient Object[] elementData;
private int size;

    其中有一個關鍵字:transient:Java的serialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用serialization機制來保存它。爲了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。

public class UserInfo implements Serializable {  
     private static final long serialVersionUID = 996890129747019948L;  
     private String name;  
     private transient String psw;  

     public UserInfo(String name, String psw) {  
         this.name = name;  
         this.psw = psw;  
     }  

     public String toString() {  
         return "name=" + name + ", psw=" + psw;  
     }  
 }  

 public class TestTransient {  
     public static void main(String[] args) {  
         UserInfo userInfo = new UserInfo("張三", "123456");  
         System.out.println(userInfo);  
         try {  
             // 序列化,被設置爲transient的屬性沒有被序列化  
             ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(  
                     "UserInfo.out"));  
             o.writeObject(userInfo);  
             o.close();  
         } catch (Exception e) {  
             // TODO: handle exception  
             e.printStackTrace();  
         }  
         try {  
             // 重新讀取內容  
             ObjectInputStream in = new ObjectInputStream(new FileInputStream(  
                     "UserInfo.out"));  
             UserInfo readUserInfo = (UserInfo) in.readObject();  
             //讀取後psw的內容爲null  
             System.out.println(readUserInfo.toString());  
         } catch (Exception e) {  
             // TODO: handle exception  
             e.printStackTrace();  
         }  
     }  
 }

    被標記爲transient的屬性在對象被序列化的時候不會被保存。

構造方法

    ArrayList提供了三種方式的構造器。

 // ArrayList帶容量大小的構造函數。
 public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    //ArrayList無參數構造參數,默認容量10
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }

     // 創建一個包含collection的ArrayList   
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray(); //調用toArray()方法把collection轉換成數組 
        size = elementData.length; //把數組的長度賦值給ArrayList的size屬性
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

    在這有一個地方需要注意下,就是在JDK1.6中無參數的構造方法是這麼寫的:

  // ArrayList無參構造函數。默認容量是10。    
    public ArrayList() {    
        this(10);    
    }   

    在1.7前,會默認在內存中直接分配10個空間,但是在1.7有了改變,會先在內存中分配一個對象的內存空間,但是這個對象是沒有長度的。但是在你進行添加的時候,默認的會去拿對象的默認大小來作比較。

    

ArrayList的動態擴容(核心)

    當ArrayList進行add操作的時候,如果添加的元素超出了數組的長度,怎麼辦?

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    add方法會去調用下面的方法,根據傳入的最小需要容量minCapacity來和數組的容量長度對比,若minCapactity大於或等於數組容量,則需要進行擴容。

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        //超出了數組可容納的長度,需要進行動態擴展
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    擴容的時候會去調用grow()方法來進行動態擴容,在grow中採用了位運算,我們知道位運算的速度遠遠快於整除運算:


private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//這纔是動態擴展的精髓,看到這個方法,ArrayList瞬間被打回原形
private void grow(int minCapacity) {
     int oldCapacity = elementData.length;
     //首先得到數組的舊容量,然後進行oldCapacity + (oldCapacity >> 1),將oldCapacity 右移一位,其效果相當於oldCapacity /2,整句的結果就是設置新數組的容量擴展爲原來數組的1.5倍
     int newCapacity = oldCapacity + (oldCapacity >> 1);
     //再判斷一下新數組的容量夠不夠,夠了就直接使用這個長度創建新數組, 
     //不夠就將數組長度設置爲需要的長度
     if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;
     //判斷有沒超過最大限制,如果超出限制則調用hugeCapacity
     if (newCapacity - MAX_ARRAY_SIZE > 0)
         newCapacity = hugeCapacity(minCapacity);
     //將原來數組的值copy新數組中去, ArrayList的引用指向新數組
     //這兒會新創建數組,如果數據量很大,重複的創建的數組,那麼還是會影響效率,
     //因此鼓勵在合適的時候通過構造方法指定默認的capaticy大小
     elementData = Arrays.copyOf(elementData, newCapacity);
 }
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

    有一點需要注意的是,容量拓展,是創建一個新的數組,然後將舊數組上的數組copy到新數組,這是一個很大的消耗,所以在我們使用ArrayList時,最好能預計數據的大小,在第一次創建時就申請夠內存。

    看一下JDK1.6的動態擴容的實現原理:

public void ensureCapacity(int minCapacity) {
      modCount++;
     int oldCapacity = elementData.length;
     if (minCapacity > oldCapacity) {
         Object oldData[] = elementData;
         int newCapacity = (oldCapacity * 3)/2 + 1;
             if (newCapacity < minCapacity)
         newCapacity = minCapacity;
             // minCapacity is usually close to size, so this is a win:
             elementData = Arrays.copyOf(elementData, newCapacity);
     }
     }

    從代碼上,我們可以看出區別:
    第一:在容量進行擴展的時候,其實例如整除運算將容量擴展爲原來的1.5倍加1,而jdk1.7是利用位運算,從效率上,jdk1.7就要快於jdk1.6。
    第二:在算出newCapacity時,其沒有和ArrayList所定義的MAX_ARRAY_SIZE作比較,爲什麼沒有進行比較呢,原因是jdk1.6沒有定義這個MAX_ARRAY_SIZE最大容量,也就是說,其沒有最大容量限制的,但是jdk1.7做了一個改進,進行了容量限制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章