近在回顧數(shù)據(jù)結(jié)構(gòu),想到JDK這樣好的代碼資源不利用有點可惜,這是第一篇,花了心思。篇幅有點長,希望想看的朋友認真看下去,提出寶貴的意見。  :)

 

類繼承體系圖                     

                                   大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

內(nèi)部原理

ArrayList 的3個字段

private transient Object[] elementData;      //對象數(shù)組,用于存儲 持有對象的 引用
 private int size;                           //代表了 ArrayList 的長度。隨著插入 刪除 添加 而改變。
 protected transient int modCount = 0;      //從AbstractList繼承得到,這個字段最后介紹,先忽視它。

 

 

ArrayList保存的真的是對象嗎?
elementData 是一個Object 數(shù)組,這是為了兼容任何類型(Java泛型是所有實際類型共享一份代碼模板的?。。。?。數(shù)組保存的實質(zhì)是持有對象的引用(reference)。引用又可以理解為 對象的“遙控器”(如下圖)。

 

從底層點的角度說,引用實質(zhì)是指針的化身,因此ArrayList即便持有很多個對象,其本身(或者說elementData 數(shù)組)保存的只不過是引用而已,內(nèi)存開銷不會太大。


所以要強調(diào)的是對于保存引用類型,java中的數(shù)組,和各種集合類,持有的是對象的引用,而不是對象本身。

 

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

 

構(gòu)造函數(shù)

空ArrayList 的優(yōu)化  : new 一個空的ArrayList是很常見的做法,為了不讓每個空ArrayList都創(chuàng)建一個空數(shù)組實例,ArrayList內(nèi)部有一個用于共享,靜態(tài)的空數(shù)組對象。當創(chuàng)建空的ArrayList時,內(nèi)部的 elementData 就會指向這個共享的空數(shù)組: EMPTY_ELEMENTDATA
 
同時也會發(fā)現(xiàn):當new 一個空ArrayList 時,不會馬上在內(nèi)存中分配  DEFAULT_CAPACITY =10  個長度的elementData數(shù)組,而是等到第一個元素被添加時,才會去申請分配默認長度 為10 的elementData 數(shù)組。

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

private static final Object[] EMPTY_ELEMENTDATA = {};     //用于所有空ArrayList共享的 空數(shù)組,注意它是靜態(tài)字段。
 private static final int DEFAULT_CAPACITY = 10;           //第一次分配容量時,默認的初始容量大小////////////////////////////////////////////// 
 public ArrayList() {        super();        this.elementData = EMPTY_ELEMENTDATA;             //指向空數(shù)組}

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

當然,如果在new 時指定了 初始容量initCapacity,就會立刻 創(chuàng)建一個 長度為 initialCapacity 大小的 elementData 數(shù)組

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 ArrayList( (initialCapacity < 0  IllegalArgumentException("Illegal Capacity: "+.elementData =

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

也可以從另一個一個集合構(gòu)造

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
}

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

容量增長策略

雖然ArrayList 號稱 動態(tài)數(shù)組,其長度自動調(diào)整,但是內(nèi)部 保存持有對象的引用的數(shù)組elementData卻是一個普通的數(shù)組(Java數(shù)組的長度是不可變的),因此,ArrayList就必須實現(xiàn)容量擴展操作,在elementData 滿 時,讓elementData指向長度更大的數(shù)組。

以保證在任何時候:elementData.length >= size

elementData 的長度就 是 ArrayList 的容量(capacity)。
 
以下是關(guān)于容量調(diào)配的API(綠色標記的是public方法,紅色的是private)

 

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 


查看源碼時,我發(fā)現(xiàn),向ArrayList 中添加新元素的時,都會先 使用 ensureCapacityInternal 這個函數(shù)去檢查容量是否夠用。如圖中,參數(shù)為 size+1 ,也就是要保證容量至少為size+1個,這樣新元素才能放得下。我們?nèi)タ纯催@個函數(shù)的實現(xiàn)。

 大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

/*函數(shù)作用:用于確保 ArrayList 的容量至少有 參數(shù)指定的minCapacity個*/
 
 private void ensureCapacityInternal(int minCapacity) {        if (elementData == EMPTY_ELEMENTDATA) {                       //這個if用于處理 往 空ArrayList中添加第一個元素時的情況
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
 
        ensureExplicitCapacity(minCapacity);                         //內(nèi)部又委托了這個函數(shù),繼續(xù)往下看
    }

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 /*函數(shù)作用:用于確保 ArrayList 的容量至少有 參數(shù)指定的minCapacity個*/private void ensureExplicitCapacity(int minCapacity) {
        modCount++; 
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)     //如果當前容量 真的比 要求的 minCapacity 小 
            grow(minCapacity);                        //那就交給grow函數(shù)去擴張容量去吧。
    }

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

最終的增長邏輯都是委托g(shù)row() 方法實現(xiàn)的。只要調(diào)用了grow這個函數(shù),就一定會使容量增加。我們來看看容量到底是怎么增加的。

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

private void grow(int minCapacity) {        // overflow-conscious code
        int oldCapacity = elementData.length;        int newCapacity = oldCapacity + (oldCapacity >> 1);      //先讓 newCapacity = 原來的1.5倍,這是默認的增長策略。                                                                 //再接著來判斷這個策略是否合適
 
        if (newCapacity - minCapacity < 0)                       //如果變?yōu)樵瓉淼?.5倍還是不夠                     
            newCapacity = minCapacity;                           //那就要多少給多少吧。
 
        if (newCapacity - MAX_ARRAY_SIZE > 0)        //如果要求的容量超大,(大于MAX_ARRAY_SIZE),這個時候就必須慎重。怎么辦?
           newCapacity = hugeCapacity(minCapacity); //交給 hugeCapacity函數(shù)去決定到底給多少,這個函數(shù)會返回  MAX_ARRAY_SIZE 或者 Integer.MAX_VALUE。 
        elementData = Arrays.copyOf(elementData, newCapacity);   //經(jīng)過一輪輪的判斷,終于定下了新的容量,好,調(diào)整elementData的容量為 newCapacity!
    }

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

總結(jié)如下,有3種情況:

注:newCapacit 為需求容量,oldCapacity原來的容量

1、oldCapacity       <  newCapacit  <= oldCapacity * 1.5  

平緩增長的正常情況。例如原來容量為10 ,現(xiàn)在期望達到11個,則會擴展 調(diào)整到  10 + (10 >> 1)  =15個   ,也就是變?yōu)?原來的1.5倍。 
 
2、oldCapacity*1.5   < newCapacit   <= MAX_ARRAY_SIZE  

默認的1.5倍增長還不夠,期望的容量有點大。例如原來容量為10 ,現(xiàn)在請求100個,那么就會實打?qū)嵉臄U展到 100 個的容量。
 
3、MAX_ARRAY_SIZE    < newCapacit    <=  Interger.MAX_VALUE  

極端情況,期望的容量超大 。在這個情況下,newCapacity的值最終是由hugeCapacity函數(shù)決定的,它會返回  MAX_ARRAY_SIZE 或者 Integer.MAX_VALUE

 

而 MAX_ARRAY_SIZE是一個比Integer.MAX_VALU小8 的數(shù) ,為什么要小8呢,我很奇怪。看了注釋解釋大致如下。

 //有些Java虛擬機會在數(shù)組內(nèi)存的頭部保留 一些字節(jié)的容量來存儲這個內(nèi)存塊的相關(guān)信息,因此,數(shù)組的 安全最大長度是 : Integer.MAX_VALUE - 8private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

 

所以,ArrayList 的最多容量不要超過 MAX_ARRAY_SIZE  (Integer.MAX_VALUE - 8)  , 也就是 2147483639 個 (瘋子才會使用這么多吧),否則是會拋異常的。

 

 

 

下面還有2個是公開的接口

公開接口:避免多余的浪費,讓容量裁剪為和size一樣大

 public void trimToSize() {
        modCount++;        if (size < elementData.length) {
            elementData = Arrays.copyOf(elementData, size);
        }
 }

 

公開接口:一次性決定ArrayList 的最大容量,避免半路上不斷的調(diào)整容量帶來開銷(如果你真的很確定你需要的ArrayList 的最大長度,否則沒必要

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

public void ensureCapacity(int minCapacity) {        int minExpand = (elementData != EMPTY_ELEMENTDATA)            // any size if real element table
            ? 0            // larger than default for empty table. It's already supposed to be            // at default size.            : DEFAULT_CAPACITY; 
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

總結(jié):默認情況下,ArrayList 平緩增長時,每次增長為原來的容量的1.5倍。

 

 

 

 

順序表的短板:插入和刪除

先看一道試題:

在java中,下面哪一個復制數(shù)組的方法最高效?

 

A、循環(huán)賦值

B、Arrays.copyOf    

C、System.arraycopy

D、clone

 

正確答案: C 

 

這個函數(shù)讓我想到了C語言標準庫中的 memmove 函數(shù),它們的功能真的很像。而且 System.arraycopy  是 native方法,實現(xiàn)會不會就是 memmove 呢???也許吧,可惜我看不到源碼。下面介紹 System.arraycopy這個函數(shù)。

 

 

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,                                        int length); 
 
/*從數(shù)組src中拷貝元素到 dest中 。我們把src數(shù)組叫做源數(shù)組,把dest數(shù)組叫做目的數(shù)組
 
     src      源數(shù)組的引用
     srcPos   源數(shù)組的拷貝起始索引
     dest     目的數(shù)組的引用
     destPos  目的數(shù)組的拷貝起始索引
     length   有l(wèi)ength個元素會被拷貝         
 
     如果參數(shù) src 和 dest 引用相同的數(shù)組對象(注意,它允許內(nèi)存重疊),則復制的執(zhí)行過程就好像首先將 srcPos 到 srcPos+length-1 位置的組件復制到
     一個帶有 length 組件的臨時數(shù)組,然后再將此臨時數(shù)組的內(nèi)容復制到目標數(shù)組的 destPos 到 destPos+length-1 位置一樣。
     這就好像是C語言標準庫中的memmove
 
    使用注意:
          1、src 和 dest不能為null,否則會拋出NullPointerExcaption異常
          2、src和 dest 的類型要兼容或者一樣。
          3、你必須精確把握好起始索引和拷貝長度的關(guān)系,有差錯就是拋異常。 
*/

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

ArrayList在刪除和插入 移動數(shù)組元素時,就是使用的這個API。我們來看看。

 

 

插入元素

插入一個元素到指定位置

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

public void add(int index, E element) {
        rangeCheckForAdd(index);   //檢查index 的合法性:邏輯如下 ,后面不再列出
        /*
                if (index > size || index < 0)
                    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));   
        */
 
 
        ensureCapacityInternal(size + 1);    // 確保容量足夠 
        //elementData 的元素的移動,讓出空間
        System.arraycopy(elementData, index, elementData, index + 1,size - index);
 
        elementData[index] = element;   //插入新的元素的引用
        size++;
    }

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

插入一個集合的元素到指定的位置

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);      //index 合法性檢查 
 
        Object[] a = c.toArray();      //先轉(zhuǎn)化為數(shù)組
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // 確保容量足夠
 
        int numMoved = size - index;        if (numMoved > 0)              //原來的數(shù)組讓出空間
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved); 
        //將新的元素拷貝到騰出的空間去。
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;        return numNew != 0;
    }

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

畫個圖打開你的腦洞(插入2個元素的集合到ArrayList 的開頭)

 

 大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

 

 

 

 刪除元素

從集合中刪除元素只是讓保存這個對象的引用為null,讓GC卻確定什么時候清理這個對象。因為java沒有free , delete 。

 

 

刪除所有的元素 clear

 

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

/* 清空ArrayList中的所有持有對象。讓size為0.
       實質(zhì)操作是讓保存持有對象的引用的數(shù)組elementData 的元素全部為null。
       所以我們會試想:如果在清空操作后 ,沒有其它引用指向ArrayList原來持有的對象了,那么這個對象就會等待被GC 回收。

       例子:
        ArrayList<Person>  arr = new ArrayList<Person>();

        Person p1 = new Person();

        arr.add(p1);             //添加第一個持有對象
        arr.add(new Person());   //添加第二個持有對象,匿名的。

        arr.clear();             //第一個持有對象因為還有引用p1 的指向,所以暫時不會等待GC回收
                                 //第二個對象由于沒有引用指向。則會等待GC回收。     */



    public void clear() {
        modCount++;        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

 

按對象刪除:刪除第一個找到的元素,立刻返回。如果沒找到,則什么也不做,返回false 。如果o不為null,則調(diào)用equals方法去查找。時間復雜度為O(n)。

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

public boolean remove(Object o) {        if (o == null)     //如果要刪除null        {            for (int index = 0; index < size; index++)                if (elementData[index] == null) //從前往后遍歷查找,一旦找到為null ,刪除之,返回                {
                    fastRemove(index);           //fastRemove 是對 System.arraycopy 的封裝
                    return true;
                }
        }        else        //如果要刪除某個真實存在的對象        {            for (int index = 0; index < size; index++)                if (o.equals(elementData[index]))  //從前往后遍歷查找,一旦找到,刪除之,返回                {
                    fastRemove(index);           //fastRemove 是對 System.arraycopy 的封裝
                    return true;
                }
        }        return false;
    }

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

按索引刪除:刪除指定索引上的元素。主要耗時是在System.arraycopy 的執(zhí)行上 。

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);        int numMoved = size - index - 1;        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

只保留  ArrayList中 和 集合c  相同元素,其它的都刪除。相同是指調(diào)用equals返回 true (或者都是null)

public boolean retainAll(Collection<?> c) {        return batchRemove(c, true);
    }

 歡迎轉(zhuǎn)載,請注明出處:www.cnblogs.com/lulipro

 為了獲得更好的閱讀體驗,請訪問原博客地址。


http://www.cnblogs.com/lulipro/p/6553816.html