1. 程式人生 > >ArrryList與linkedList的區別以及ArrayList的原始碼實現原理

ArrryList與linkedList的區別以及ArrayList的原始碼實現原理

Java中ArrayList和LinkedList區別

1.ArrayList是實現了基於動態陣列的資料結構,LinkedList基於連結串列的資料結構。
2.對於隨機訪問get和set,ArrayList覺得優於LinkedList,因為LinkedList要移動指標。
3.對於新增和刪除操作add和remove,LinedList比較佔優勢,因為ArrayList要移動資料。

ArrayList的底層實現原理

一、 ArrayList概述:

ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。
ArrayList不是執行緒安全的,只能用在單執行緒環境下,多執行緒環境下可以考慮用Collections.synchronizedList(List l)函式返回一個執行緒安全的ArrayList類,也可以使用concurrent併發包下的CopyOnWriteArrayList類。
ArrayList實現了Serializable介面,因此它支援序列化,能夠通過序列化傳輸,實現了RandomAccess介面,支援快速隨機訪問,實際上就是通過下標序號進行快速訪問,實現了Cloneable介面,能被克隆。

每個ArrayList例項都有一個容量,該容量是指用來儲存列表元素的陣列的大小。它總是至少等於列表的大小。隨著向ArrayList中不斷新增元素,其容量也自動增長。自動增長會帶來資料向新陣列的重新拷貝,因此,如果可預知資料量的多少,可在構造ArrayList時指定其容量。在新增大量元素前,應用程式也可以使用ensureCapacity操作來增加ArrayList例項的容量,這可以減少遞增式再分配的數量。
注意,此實現不是同步的。如果多個執行緒同時訪問一個ArrayList例項,而其中至少一個執行緒從結構上修改了列表,那麼它必須保持外部同步。

二、 ArrayList的實現:

對於ArrayList而言,它實現List介面、底層使用陣列儲存所有元素。其操作基本上是對陣列的操作。下面我們來分析ArrayList的原始碼:

1) 私有屬性:
ArrayList定義只定義類兩個私有屬性:
這裡寫圖片描述
在這裡transient的是為了不想序列化改物件陣列。當持久化物件時,可能有一個特殊的物件資料成員,.我們不想用serialization機制儲存它。為了在一個特定物件的一個域上關閉serialization,可以在這個域前加上關鍵字transient。
2) 構造方法:
ArrayList提供了三種方式的構造器,可以構造一個預設初始容量為10的空列表、構造一個指定初始容量的空列表以及構造一個包含指定collection的元素的列表,這些元素按照該collection的迭代器返回它們的順序排列的。

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+                                               initialCapacity);
        }
    }
    ----------------------------------------------------------
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    ----------------------------------------------------------
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

3) 元素儲存:

ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、
addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)
這些新增元素的方法。下面我們一一講解:

這裡寫圖片描述
4) 元素讀取:
只需要傳入你想讀取元素的下標,就可以了,如果沒有此下標就會報空指標異常

  // 返回此列表中指定位置上的元素。  
 public E get(int index) {  
    RangeCheck(index);  
    return (E) elementData[index];  
  }

5)元素的刪除:
傳入你想刪除元素的下標,然後進行刪除,沒有會報空指標異常,刪除成功之後,如果刪除的是末尾的元素,沒什麼問題,但還是當你刪除的是其中的某一個元素就會牽扯到資料的移位。具體的實現程式碼如下:

 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; 
        return oldValue;
    }

首先是檢查範圍,修改modCount,保留將要被移除的元素,將移除位置之後的元素向前挪動一個位置,將list末尾元素置空(null),返回被移除的元素。

remove(Object o)
// 移除此列表中首次出現的指定元素(如果存在)。這是應為ArrayList中允許存放重複的元素。
public boolean remove(Object o) {
// 由於ArrayList中允許存放null,因此下面通過兩種情況來分別處理。
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 類似remove(int index),移除列表中指定位置上的元素。
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
}
首先通過程式碼可以看到,當移除成功後返回true,否則返回false。remove(Object o)中通過遍歷element尋找是否存在傳入物件,一旦找到就呼叫fastRemove移除物件。為什麼找到了元素就知道了index,不通過remove(index)來移除元素呢?因為fastRemove跳過了判斷邊界的處理,因為找到元素就相當於確定了index不會超過邊界,而且fastRemove並不返回被移除的元素。下面是fastRemove的程式碼,基本和remove(index)一致。

 private void fastRemove(int index) {  
          modCount++;  
          int numMoved = size - index - 1;  
          if (numMoved > 0)  
              System.arraycopy(elementData, index+1, elementData, index,  
                               numMoved);  
          elementData[--size] = null; // Let gc do its work  
  }
先看下面這個例子
    ArrayList<Integer> ints = new ArrayList<Integer>(Arrays.asList(0, 1, 2,  
                 3, 4, 5, 6));  
         // fromIndex low endpoint (inclusive) of the subList  
         // toIndex high endpoint (exclusive) of the subList  
        ints.subList(2, 4).clear();  
         System.out.println(ints);  
輸出結果是[0, 1, 4, 5, 6],結果是不是像呼叫了removeRange(int fromIndex,int toIndex)!哈哈哈,就是這樣的。但是為什麼效果相同呢?是不是呼叫了removeRange(int fromIndex,int toIndex)呢?

6) 調整陣列容量ensureCapacity: 

   從上面介紹的向ArrayList中儲存元素的程式碼中,我們看到,每當向陣列中新增元素時,都要去檢查新增後元素的個數是否會超出當前陣列的長度,如果超出,陣列將會進行擴容,以滿足新增資料的需求。陣列擴容通過一個公開的方法ensureCapacity(int minCapacity)來實現。在實際新增大量元素前,我也可以使用ensureCapacity來手動增加ArrayList例項的容量,以減少遞增式再分配的數量。

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

從上述程式碼中可以看出,陣列進行擴容時,會將老陣列中的元素重新拷貝一份到新的陣列中,每次陣列容量的增長大約是其原容量的1.5倍。這種操作的代價是很高的,因此在實際使用時,我們應該儘量避免陣列容量的擴張。當我們可預知要儲存的元素的多少時,要在構造ArrayList例項時,就指定其容量,以避免陣列擴容的發生。或者根據實際需求,通過呼叫ensureCapacity方法來手動增加ArrayList例項的容量。

Object oldData[] = elementData;//為什麼要用到oldData[]
乍一看來後面並沒有用到關於oldData, 這句話顯得多此一舉!但是這是一個牽涉到記憶體管理的類, 所以要了解內部的問題。 而且為什麼這一句還在if的內部,這跟elementData = Arrays.copyOf(elementData, newCapacity); 這句是有關係的,下面這句Arrays.copyOf的實現時新建立了newCapacity大小的記憶體,然後把老的elementData放入。好像也沒有用到oldData,有什麼問題呢。問題就在於舊的記憶體的引用是elementData, elementData指向了新的記憶體塊,如果有一個區域性變數oldData變數引用舊的記憶體塊的話,在copy的過程中就會比較安全,因為這樣證明這塊老的記憶體依然有引用,分配記憶體的時候就不會被侵佔掉,然後copy完成後這個區域性變數的生命期也過去了,然後釋放才是安全的。不然在copy的的時候萬一新的記憶體或其他執行緒的分配記憶體侵佔了這塊老的記憶體,而copy還沒有結束,這是個問題。