java集合框架-List集合ArrayList和LinkedList詳解

List 集合源碼剖析

✅ ArrayList

image

底層是基於數組,(數組在內存中分配連續的內存空間)是對數組的升級,長度是動態的。java

數組默認長度是10,當添加數據超越當前數組長度時,就會進行擴容,擴容長度是以前的1.5倍,要對以前的數組對象進行復制,因此只有每次擴容時相對性能開銷大一些。node

源碼(jdk 1.8):git

1. 添加元素(非指定位置)

// 1. 添加元素
 
 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 每次添加元素都要對容量評估
        elementData[size++] = e;
        return true;
 }
 
 // 2. 評估容量
 
 private void ensureCapacityInternal(int minCapacity) {
        
        // 若果數組對象仍是默認的數組對象
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
 }
 
 // 3. 進一步評估是否要進行擴容
 
  private void ensureExplicitCapacity(int minCapacity) {
  
        modCount++; // 記錄ArrayList結構性變化的次數

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
  }

步驟3中
if (minCapacity - elementData.length > 0) grow(minCapacity);
minCapacity 大於當前數組對象長度時 才進行擴容操做,也就是執行步驟 4的代碼github

// 4.複製產生新的數組對象並擴容
 
  private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

2. 指定位置添加元素

public void add(int index, E element) {
    
        // 1
        rangeCheckForAdd(index);
        // 2
        ensureCapacityInternal(size + 1); 
        // 3
        System.arraycopy(elementData, index, elementData, index + 1,size - index);
        // 4
        elementData[index] = element;
        // 5
        size++;
    }
  1. rangeCheckForAdd(index); 評估插入元素位置是否合理數組

  2. ensureCapacityInternal(size + 1); 檢查數組容量是否有當前元素數量 size +1 個大,由於後續進行數組複製要多出一個元素安全

  3. 數組複製數據結構

System.arraycopy(elementData, index, elementData, index + 1,size - index);

System.arraycopy(src, srcPos, dest, destPos , length);

src:源數組;srcPos:源數組要複製的起始位置;index 是要插入元素的位置,因此要從當前開始複製性能

dest:目的數組; destPos:目的數組放置的起始位置;複製好的元素要放在插入位置的後面 因此 index+1ui

length:複製的長度。包括插入位置和後面的元素 = 當前元素數 - 插入位置this

  1. 步驟執行元素賦值

  2. 步驟元素長度+1

若是ArrayLisy集合不指定位置添加元素,默認往數組對象的後面追加,因此數組對象的其餘元素位置不變,沒有什麼性能開銷,若是元素插入到數組對象的前面,並且是越往前,從新排序的數組元素就會越多性能開銷越大,固然經過上述源碼介紹中看到,經過數組複製的方式排序對性能影響也不大,

3.查找元素

// 獲取指定位置元素的源碼
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

直接返回當前查找位置的數組對象對應的下標位置的元素,高性能,速度很快。

4. ArraList 和 Vector

ArraList 和 Vector 都是基於數組實現,它倆底層實現原理幾乎相同,只是Vector是線程安全的,ArrayLsit不是線程安全的,它倆的性能也相差無幾。

✅ LinkedList

  • LinkedList使用循環雙向鏈表數據結構,它和基於數組的List相比是大相徑庭的,在內存中分配的空間不是連續的。
  • LinkedList是由一系列表項組成的,包括數據內容、前驅表項、後驅表項,或者說先後兩個指針
  • 無論LinkedList集合是否爲空,都有一個header表項。(前驅表項指向 最後一個 元素,後驅表項指向 第一個元素)

雙向鏈表

image

表項

image

1. 添加元素

//1. 添加元素,不指定位置會添加到最後一個
    
   public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    //2. 添加到最後一位(每次添加插入元素都會建立一個Node對象)
    
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null); // 雙向鏈表的最後一個表項沒有 後驅指針
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
    
     //3. 建立表項
     private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
        
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

2. 添加指定位置元素

public void add(int index, E element) {
        checkPositionIndex(index); // 檢查位置是否合理

        if (index == size)
            linkLast(element);     // 若是插入位置等於集合元素長度就日後追加
        else
            linkBefore(element, node(index)); // 不然在當前位置元素前面建立新節點
    }
    
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        // 步驟一
        final Node<E> pred = succ.prev;  
        // 步驟二
        final Node<E> newNode = new Node<>(pred, e, succ);
        // 步驟三
        succ.prev = newNode;
        // 步驟四
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

步驟一:獲取當前元素(未插入目前要插入的元素前)前指針指向的節點

步驟二:建立新節點,一個表項,前驅表項是前面的節點 步驟一獲得,後驅表項指向的時當前位置的節點(未插入目前要插入的元素前)

步驟三:從新設置當前位置的節點(未插入目前要插入的元素前)的前驅指針指向的節點,也就是剛插入的建立的新節點

步驟四:是對建立新節點的前置節點的後驅表項進行修改設置

總結:對應LinkedList插入任意位置元素 咱們只需建立一個新元素節點和移動先後兩個表項的指針,其餘表項無需任何操做,性能高;

3. LinkedList集合中的第一個和最後一個元素位置是肯定的

最後一個元素和第一個元素的位置不須要遍歷整個鏈表獲取,每一個LinkedList集合不管是否爲空,都會有一個Header表項,Header表項的前驅指針始終指向最後一個元素,後驅指針指向第一個元素,因此能夠說LinkedList集合中的第一個和最後一個元素位置是肯定的。

4. 查找刪除元素

循環雙向鏈表結構中節點位置的肯定須要根據前面的元素日後遍歷查找或者後面的元素往前遍歷查找

// 1. 獲取指定位置元素

public E get(int index) {
        checkElementIndex(index); // 首先判斷位置是否有效
        return node(index).item;
}

// 2. 根據位置對節點遍歷查找

Node<E> node(int index) {
        // assert isElementIndex(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集合的長度很大,則每次查找元素都會遍歷不少次,性能影響也會更大

刪除任意位置的元素,是先要找到該元素,因此須要上一步提到的查找操做

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index)); // node(index) 是查找元素
    }

ArrayList和LinkedLisy對比

🍡 實現方式

ArrayList 是基於數組實現,內存中分配連續的空間,須要維護容量大小。

LinkedList 是基於循環雙向鏈表數據結構,不須要維護容量大小。

🍪 添加插入刪除元素

ArrayList不自定義位置添加元素和LinkedList性能沒啥區別,ArrayList默認元素追加到數組後面,而LinkedList只須要移動指針,因此二者性能相差無幾。

若是ArrayList自定義位置插入元素,越靠前,須要重寫排序的元素越多,性能消耗越大,LinkedList不管插入任何位置都同樣,只須要建立一個新的表項節點和移動一下指針,性能消耗很低。

頻繁插入刪除元素 -> 使用 LinkedList 集合

🚓 遍歷查看元素

ArrayList是基於數組,因此查看任意位置元素只須要獲取當前位置的下標的數組就能夠,效率很高,然而LinkedList獲取元素須要從最前面或者最後面遍歷到當前位置元素獲取,若是集合中元素不少,就會效率很低,性能消耗大。

頻繁遍歷查看元素 -> 使用 ArrayList 集合

🍝 ArrayList和LinkedList 都時線程不安全的

更多內容:

gitHub源碼和庫 https://github.com/ShiFengCui/collect-java

相關文章
相關標籤/搜索