1. 程式人生 > >Java集合 List實現類 ArrayList 原始碼淺析

Java集合 List實現類 ArrayList 原始碼淺析

Java集合 List實現類 ArrayList 實現淺析

文章目錄

一、List 簡述(來自ArrayList註釋)

List 介面的大小可變陣列的實現。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 介面外,此類還提供一些方法來操作內部用來儲存列表的陣列的大小。(此類大致上等同於 Vector 類,除了此類是不同步的。)

size、isEmpty、get、set、iterator 和 listIterator 操作都以固定時間執行。add 操作以分攤的固定時間 執行,也就是說,新增 n 個元素需要 O(n) 時間。其他所有操作都以線性時間執行(大體上講)。與用於 LinkedList 實現的常數因子相比,此實現的常數因子較低。

每個 ArrayList 例項都有一個容量。該容量是指用來儲存列表元素的陣列的大小。它總是至少等於列表的大小。隨著向 ArrayList 中不斷新增元素,其容量也自動增長。並未指定增長策略的細節,因為這不只是新增元素會帶來分攤固定時間開銷那樣簡單。

在新增大量元素前,應用程式可以使用 ensureCapacity 操作來增加 ArrayList 例項的容量。這可以減少遞增式再分配的數量。

注意,此實現不是同步的。 如果多個執行緒同時訪問一個 ArrayList 例項,而其中至少一個執行緒從結構上修改了列表,那麼它必須 保持外部同步。(結構上的修改是指任何新增或刪除一個或多個元素的操作,或者顯式調整底層陣列的大小;僅僅設定元素的值不是結構上的修改。)這一般通過對自然封裝該列表的物件進行同步操作來完成。如果不存在這樣的物件,則應該使用 Collections.synchronizedList 方法將該列表“包裝”起來。這最好在建立時完成,以防止意外對列表進行不同步的訪問:

 List list = Collections.synchronizedList(new ArrayList(...)); 

此類的 iterator 和 listIterator 方法返回的迭代器是快速失敗的:在建立迭代器之後,除非通過迭代器自身的 remove 或 add 方法從結構上對列表進行修改,否則在任何時間以任何方式對列表進行修改,迭代器都會丟擲 ConcurrentModificationException。因此,面對併發的修改,迭代器很快就會完全失敗,而不是冒著在將來某個不確定時間發生任意不確定行為的風險。

注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步併發修改做出任何硬性保證。快速失敗迭代器會盡最大努力丟擲 ConcurrentModificationException。因此,為提高這類迭代器的正確性而編寫一個依賴於此異常的程式是錯誤的做法:迭代器的快速失敗行為應該僅用於檢測 bug。

類圖

ArrayList繼承圖

ArrayList 原始碼實現基於 jdk 1.8

原始碼來源於 jdk 1.8

二、構造方法

ArrayList 欄位說明

// 初始預設容量為10
private static final int DEFAULT_CAPACITY = 10;	
// 存放元素的陣列
transient Object[] elementData;					
// 這個值是實際陣列包含元素的數量, 注意這個值不是陣列的大小 
private int size;		
// 長度為零的非空陣列
private static final Object[] EMPTY_ELEMENTDATA = {};	

1) 無參的構造方法

原始碼註釋: 構造一個初始容量為十的空列表。

原始碼分析: DEFAULTCAPACITY_EMPTY_ELEMENTDATA是個 {}, 即空陣列, 為什麼初始容量為10? 下面看 add 方法時再解析*

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

2) 引數為int 的構造方法

原始碼註釋: 構造具有指定初始容量的空列表。

原始碼分析: 如果引數 initcap > 0,則建立指定容量的裡邊,
如果 == 0,則建立空的陣列,引數為0的情況效果和無參構造方法一樣,因為 EMPTY_ELEMENTDATA = {}, DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {},
如果 < 0; 則丟擲異常 IllegalArgumentException(合法或不正確的引數);

   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);
       }
   }

3) 建立指定集合的列表

原始碼註釋: 構造一個包含指定集合的元素的列表,按照它們由集合的迭代器返回的順序。

原始碼分析: 引數 c 呼叫toArray()方法轉換為陣列賦給列表的屬性elementData ,
如果集合 c 元素為0, 則建立預設容量為空的列表;
如果c > 0, 則判斷型別, 再複製陣列,此處具有填充的效果*

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

三、List 方法

1. 新增元素 add(E e)

原始碼註釋: (新增一個元素到列表末尾)

原始碼分析:
新增元素前準備.:
a:陣列初始化 ensureCapacityInternal()
b: 計數器 +1 ensureExplicitCapacity()
c: 陣列是否已滿,是否需要擴容 grow()
d: 把元素新增到數組裡: elementData[size++] = e;

//  這是在AbstractList裡的一個瞬時變數用於控制list併發操作的計數器
protected transient int modCount = 0;		
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

/*
* a. 新增元素前的準備, 呼叫方法ensureCapacityInternal(size + 1) ,
* 判斷 elementData是否為 {}空的未初始化的列表,如果 == {}, 則 返回預設的大小
* 如果 elementData != {}, 則呼叫方法ensureExplicitCapacity(minCapacity)
*/
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

/*
* b.
* 判斷當前列表元素數量 + 1是否超過了elementData.length 的大小,
* 計算器 modCount++ 加1;
* 如果 >,則呼叫 grow(minCapacity)方法進行擴容;
*/
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

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

/*
* c. 擴容:
* 陣列elementData 的容量擴大為原來的 1.5倍, 然後複製元素到新陣列,
* 有原始碼可知, ArrayList運許建立的列表容量最大值為, Integer.MAX_VALUE = (2^32 - 1)
*/
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. 獲取元素 get(index)

原始碼註釋: 返回此列表中指定位置的元素。

原始碼分析: 傳入陣列的索引獲取元素前要檢查索引是否越界,
如果越界,則丟擲異常 IndexOutOfBoundsException,最後呼叫 elementData(int index) 方法直接返回元素 E;

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}
// 檢查索引是否越界
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

3.刪除指定位置的一個元素 remove(int index)

原始碼註釋: 刪除該列表中指定位置的元素。

原始碼分析: 刪除指定位置的元素,先檢查下標再獲取此元素; 最後呼叫 System 類中的arraycopy()方法把除去index位置的元素的其他元素全部左移,最後一個元素 elementData[–size] = null;最終陣列的大小不變只是末尾的一個元素置為null
最終結果就像這樣

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
[0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 14]
[0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, null]

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

關於 System.arraycopy() 方法實現這裡不做探討

    public static native void arraycopy(Object src,  int  srcPos,  dest, int destPos, length);
屬性 說明
src 源陣列
srcPos 源陣列中的起始位置
dest 目標陣列
destPos 目標資料中的起始位置
length 要複製的陣列元素的數量

4.刪除指定元素,首次出現的元素 remove(object)

原始碼註釋: 從列表中刪除指定元素的第一個出現(如果存在)。

原始碼解析: 移除首次出現的指定元素, 迴圈size長度的陣列, 如果 o == null , 則判斷(elementData[index] == null), 如果 o != null , 則 o.equals(elementData[index]),
最後呼叫 fastremove(index) 從新拷貝元素;*

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

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; // clear to let GC do its work
}

5.修改元素 set(index, element)

原始碼註釋: 用指定的元素替換此列表中指定位置的元素

原始碼解析: 修改指定位置的元素, 首先檢查index 是否越界, 然後把index 位置的元素替換位當前元素

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
  • 以上5個操作是基本的crud操作, 下面要探討List的其他操作

6.新增元素到指定位置 add(index, element)

原始碼註釋: 在此列表中的指定位置插入指定的元素。

原始碼解析: 新增元素到指定位置時, 先檢查下標是否越界, 再判斷當前陣列是否已滿,
最後複製陣列,把指定元素替換到數組裡

public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

7.獲取列表元素的數量 size()

原始碼分析: 直接返回列表的 size 屬性;

public int size() {
    return size;
}

8.判斷列表是否為空 isEmpty()

原始碼分析: 如果列表的長度為 0, 則列表為空表; 這裡所指的空是列表元素為0, 而不是 list 物件為 null

public boolean isEmpty() {
    return size == 0;
}

9.搜尋元素 indexOf(object)

原始碼註釋: 返回此列表中指定元素的第一次出現的索引,如果此列表不包含元素,則返回-1。

原始碼分析: 這裡的演算法和 remove(object) 方法一樣,從索引0開始取出每個元素對比

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

10.搜尋最後出現此元素的索引 lastIndexOf(object)

原始碼註釋: 返回此列表中指定元素的最後一次出現的索引,如果此列表不包含元素,則返回-1。

原始碼解析: 此處是從後往前遍歷的迴圈, 邏輯是 indexOf() 方法的對立;

public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

11.判斷是否包含指定元素 contains(object)

原始碼註釋: (如果列表包含此元素, 則返回 true 否則 false)

原始碼分析: 從原始碼看, 此方法是 indexOf()的擴充套件.

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

12. 清空列表 clear()

原始碼註釋: (從此列表中刪除所有元素。 此呼叫返回後,列表將為空)

原始碼分析: for迴圈把陣列所有引用置空, size = 0

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

13.list 轉陣列 toArray()

原始碼註釋: 以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的陣列。

原始碼分析: 此方法返回一個 Object[] 型別的陣列, 無法返回目標泛型的陣列, 無法強制轉換為目標陣列, 比如 (String[])list.toArray() 會丟擲異常 ClassCastException, 一般不推薦使用

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

14.list 轉陣列 toArray(T[] a) (返回執行時型別)

原始碼註釋: 以正確的順序返回一個包含此列表中所有元素的陣列(從第一個到最後一個元素); 返回的陣列的執行時型別是指定陣列的執行時型別。

原始碼分析: 從原始碼看, 如果 傳入的陣列a 小於列表的元素的數量,則呼叫 Arrays.copyOf()方法;否則 呼叫 System.arraycopy() 方法, 最後多出的部分,分配null;
推薦使用此轉換方法:

		String[] ss = {};
		System.out.println(list.toArray(ss));
		
		輸出為: [Ljava.lang.String;@3ffc5af1
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

15 . 排序 sort(Comparator<? super E> c)

原始碼註釋: 使用提供的 Comparator對此列表進行排序以比較元素。

原始碼分析: 從原始碼看, 排序也會導致併發修改的異常

    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

sort使用方法:
建立一個比較器, Comparator

public interface Comparator<T> {
	// 比較它的兩個引數的順序。返回負整數,零或正整數,
	// 因為第一個引數小於,等於或大於第二個引數。
	// 按由小到大的排序, 升序
	int compare(T o1, T o2);
}
	// 匿名內部類用法
	arrayList.sort(new Comparator<String>() {
		@Override
		public int compare(String o1, String o2) {
			if (o1 == null || o2 == null) {
				return -1;
			}
			return o1.compareTo(o2);
		}
	});
	// lambad 表示式用法
	arrayList.sort((o1, o2)-> o1.compareTo(o2));
	// 自定義型別用法
	private static class Foo {
		private int id;
		private String name;
		
		private void test() {
			List<Foo> list = new ArrayList<TestList.Foo>();
			list.add(new Foo());
			list.sort((f1, f2)-> f1.id - f2.id);
		}
	}

16. 迭代 forEach(Consumer<? super E> action)

原始碼註釋: 對Iterable的每個元素執行給定的操作,直到處理完所有元素或操作丟擲異常為止。 除非實現類另有指定,否則操作按迭代順序執行(如果指定了迭代順序)。 操作丟擲的異常將轉發給呼叫者。

原始碼解析: 從這個方法的程式碼看,如果呼叫這個方法時,併發修改會丟擲異常ConcurrentModificationException

    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

使用方式:

		// 匿名內部類形式
		list.forEach(new Consumer<Foo>() {
			@Override
			public void accept(Foo foo) {
				System.out.println(foo);
			}
		});
		
		// lambda 方式
		list.forEach((foo)-> System.out.println(foo)) ;

ArrayList 的克隆與序列化

a)克隆 clone()

原始碼註釋: 返回此ArrayList例項的淺表副本。 (元素本身不會被複制。)

原始碼分析: (淺拷貝) 正如註釋所描述的一樣, 返回的 list副本里的元素是原來元素的指標, 操作此處的元素會導致源l