Java集合源碼探究~List

在Java中,集合是咱們常常要使用的內容,而且集合也是面試的考點之一,掌握集合幫助咱們瞭解更多的內部構造。面試

List

list集合是表明的是一個元素有序的,可重複的集合。數組

雖然List中有不少子類的實現,但咱們常常用的仍是那幾個,ArrayList,LinkedList,Vector等內容。安全

ArrayList

ArrayList 是底層由數組構成的集合,可是ArrayList有哪些優勢呢?bash

  • 可以作到動態擴容,再也不侷限於設置的數組大小。
  • 繼承於List,有集合的操做方式,方便快速的操做書庫,添加,刪除,修改,遍歷等內容。

缺點多線程

  • 不是線程安全的集合,在操做多線程的時候須要採用別的集合例如Vector或者CopyOnWriteArrayList方式。

ArrayList 源碼解析

基本元素

// 默認的List 集合大小,在建立ArrayList 的時候沒有制定大小 默認是10 
    private static final int DEFAULT_CAPACITY = 10;

   // 默認的空對象
    private static final Object[] EMPTY_ELEMENTDATA = {};

   // 默認對象內容是該值
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //當前數據對象存放的地方  
    transient Object[] elementData; // non-private to simplify nested class access

    //當前數組的長度
    private int size;
    
    // 數組最大的長度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    // 改變list 大小的次數,進行增刪除數據都涉及到此數值
    protected transient int modCount = 0;
複製代碼

方法介紹

既然是數組集合,就須要涉及到數組的擴容與縮容,在原先咱們學習數組的時候就瞭解,數組的擴容與縮容都涉及到數組內的數據的遷移問題。性能

既然ArrayList 底層是數組,想固然的也須要涉及到這部份內容。學習

add方法

add 方法中涉及到增長單個元素,增長單個元素到指定位置,增長一個集合元素,增長一個集合元素到指定位置四個不一樣類型的方法,可是基本內容是相同的.ui

public boolean add(E e) {
          元素增長 ,在如今的大小上增長1 
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
     // 計算是否須要進行擴容 ,須要就進行擴容
     private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 默認的空對象 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
        // 判斷 如今list 列表中的元素 是不是空對象。 空對象 返回 最大的值 。不是空對象 返回minCapacity
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    private void ensureExplicitCapacity(int minCapacity) {
        // 擴容結構進行加1 
        modCount++;
        // 進行擴容 
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    // 擴容代碼 
    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);
    }
    
    // 計算容量 選擇 
     private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
複製代碼

從以上代碼來看庫容有如下流程來判斷this

  • 若是是新創建的集合list 加入數據
    1. 首先new出來的是集合,不給定參數的狀況下,是沒有進行任何容量的初始化的。
    2. 在執行插入add的時候,會進行容量的初始化10.
  • 已經存在的數據量的集合或者指定集合數量
    1. 在建立的時候指定數量,那麼會初始化這麼數組空內容。
    2. 執行插入的時候會進行直接插入。

以上內容新建後的流程,有內容後,就涉及到擴容的問題。spa

  • List 數組擴容。
    1. 判斷如今list裏面內容的大小是否超過設置的容量大小。
    2. 不超過不執行擴容
    3. 超過執行庫容
    4. 擴容首先擴大1.5倍的大小容量
    5. 若是該容量仍是不足以放置新增的數據,會直接擴容到最小要求的容量。
    6. 新的容量大小與最大值進行比較
    7. 存在負值狀況小於0 直接超出內容容量的大小。
    8. 大於如今最大值直接返回Integer的最大值。
    9. 說明list不是無限大小的,最大是Integer的最大值。
    10. 進行數據的copy進行數組的擴容。 代碼解釋請看上面
public void add(int index, E element) {
        // 指定位置增長數據,須要檢查該位置是否已經被安置數據若是沒有那麼執行失敗
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 進行數據的copy 工做,將該位置的數據日後面進行復制
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        而後修改該值
        elementData[index] = element;
        size++;
    }    
    
      public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    
       public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
複製代碼

整體添加流程簡單來講是這樣的:

  1. 判斷是不是新初始化的空集合或者指定了容量的集合
  2. 進行添加數據判斷是否須要擴容,或者先判斷指定位置數據是否存在
  3. 擴容後的數據遷移。

ArrayList 集合容量擴容會致使性能問題,Java中複製是須要消耗內容空間,建立一樣數量的對象大小,特別是大批量數據進行庫容容易致使性能降低。

set get

set get 方法沒有須要多說的,根據下標進行數據的讀取與插入,小標註意不要超過集合大小。

remove

remove 方法在數組中會致使數組的結構的破壞,刪除數據也須要進行數據的遷移。

// 移除指定元素
    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);
        //最後的一位置位null  讓gc來處理
        elementData[--size] = null; // clear to let GC do its work
        返回移除內容
        return oldValue;
    }
複製代碼
// 批量進行刪除
private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                // 判斷不被 刪除的元素是否在集合中 集合是 0  1 2  3 4  5   刪除 1 3 complement = true 
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
        
            // Preserve behavioral compatibility with AbstractCollection, 
            // even if c.contains() throws. 1 3 2 3 4 5  異常執行復制copy  這裏主要是存在可能刪除的元素不在集合中
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work 0 1  size = 6  w = 2 
                for (int i = w; i < size; i++)     
                    elementData[i] = null;
                modCount += size - w;  
                size = w;       
                modified = true; 
            }
        }
        return modified;
    }
複製代碼

刪除代碼中主要涉及到

  • 刪除指定元素,都涉及到數組內容的拷貝。

循環

  • for循環,根據指針進行循環,比較快速
  • foreach與迭代器循環數據比較適合鏈表式的集合數據
  • 刪除集合中的數據使用迭代器,若是使用for指針循環刪除數據容易出現異常。
相關文章
相關標籤/搜索