Java架構直通車——ArrayList和LinkedList底層原理解析

ArrayList實現原理

初始化

ArrayList的底層是一個動態數組,初始化時,ArrayList首先會對傳進來的初始化參數initalCapacity進行判斷:

  • 如果參數等於0,則將數組初始化爲一個空數組,
  • 如果不等於0,將數組初始化爲一個容量爲10的數組。

不過,與HashMap一樣,ArrayList也是一個懶加載的方式。

擴容方式

當數組的大小大於初始容量的時候(比如初始爲10,當添加第11個元素的時候),就會進行擴容,新的容量爲舊的容量的1.5倍。
擴容的時候,會以新的容量建一個原數組的拷貝,修改原數組,指向這個新數組,原數組被拋棄,會被GC回收。

add()方法

對於順序插入,ArrayList特別快,沒什麼好說的。
這裏想說的的是隨機插入,隨機插入的實現方式爲:原來數組插入位置後面的元素按順序複製到原數組插入位置+1的位置。

	public void add(int index, E element) {
        /*判斷插入的索引是否符合ArrayList範圍,在0 和 size之間,size是ArrayList實際元素個數,不包括底層數組的null元素*/
        rangeCheckForAdd(index);
        /*擴容機制:判斷添加是否需要進行擴容*/
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        /*將舊數組拷貝到一個新數組中,參數:被複制的原數組, 被複制數組的第幾個元素開始複製, 複製的目標數組, 從目標數組index + 1位置開始粘貼, 複製的元素個數,*/
        System.arraycopy(elementData, index, elementData, index + 1, size - index);           
        /*將新元素賦予該下標*/
        elementData[index] = element;
        /*元素個數+1*/
        size++;
    }

實現接口

ArrayList實現了Serializable接口,因此它支持序列化,能夠通過序列化傳輸,實現了RandomAccess接口,支持快速隨機訪問,實際上就是通過下標序號進行快速訪問,實現了Cloneable接口,能被克隆。

爲什麼ArrayList裏面的elementData爲什麼要用transient來修飾?

看源碼可以知道,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. 
      */  
     private transient Object[] elementData;  
   
     /** 
      * The size of the ArrayList (the number of elements it contains). 
      * 
      * @serial 
      */  
     private int size;

elementData裏面不是所有的元素都有數據,因爲容量的問題,elementData裏面有一些元素是空的,這種是沒有必要序列化的。ArrayList的序列化和反序列化依賴writeObject和readObject方法來實現。可以避免序列化空的元素。

/**
     * 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();
        }
    }

線程安全與否

ArrayList不是線程安全的,只能用在單線程環境下,多線程環境下可以考慮用Collections.synchronizedList(List l)函數返回一個線程安全的ArrayList類,也可以使用concurrent併發包下的CopyOnWriteArrayList類。

LinkedList實現原理

初始化

Linkedlist底層是一個雙向鏈表,頭尾都包含一個不含任何元素的dummy節點。

//指向頭節點的變量first   
transient Node<E> first; 
//指向尾節點的變量last   
 transient Node<E> last;  

在這裏插入圖片描述

其初始化方法很簡單(也就是什麼都不用做):

	/**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

add()方法

LinkedList包括了頭插和尾插(很容易理解):

	public void addFirst(E e) {
        linkFirst(e);
    }
   	public void addLast(E e) {
        linkLast(e);
    }
    /**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

直接使用add()方法,其實就是調用的尾插:

	public boolean add(E e) {
        linkLast(e);
        return true;
    }

而帶index的插入:

	public void add(int index, E element) {
		//檢查插入位置的合法性
        checkPositionIndex(index);
		
        if (index == size)
        	//如果插入位置在尾部直接使用尾插
            linkLast(element);
        else
        	//如果index在前半段就使用頭插,否則使用尾插
            linkBefore(element, node(index));
    }

	/**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);
		//如果index在前半段就使用頭插,否則使用尾插
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

實現接口

LinkedList 是一個繼承於AbstractSequentialList的雙向鏈表。它也可以被當作堆棧、隊列或雙端隊列進行操作
LinkedList 實現了Cloneable接口,即覆蓋了函數clone(),能克隆。
LinkedList 實現java.io.Serializable接口,這意味着LinkedList支持序列化,能通過序列化去傳輸。

線程安全與否

同樣,也不是線程安全的。可以考慮:

  1. Collections.synchronizedList(new LinkedList<>());
  2. ConcurrentLinkedQueue

總結:面試如何介紹ArrayList和LinkedList

  1. 數據結構:
    ArrayList底層是數組,採用懶加載的方式初始化,默認長度是10。添加元素如果超過數組長度,採用1.5倍大小進行擴容。
    LinkedList底層是雙向鏈表,使頭尾使用不包含有效元素的dummy節點。
  2. 查詢效率:
    由於底層結構的關係,ArrayList實現了隨機訪問,查詢更快。而LinkedList查找某個元素的時間複雜度是O(n)。
  3. 增刪效率:
    ArrayList對於順序增加非常快,但是指定索引的增刪,需要移動索引後面的元素,效率不高。
    LinkedList增刪一般來說很快,只需要刪除指定節點並且連同前後兩個節點即可。但是對於指定了索引的刪除,就需要根據索引位置,選擇從頭節點或者尾節點出發找到索引,然後刪除。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章