Java集合-ArrayList源码解析-JDK1.8
◆
ArrayList簡介
◆
ArrayList 是一個數組隊列,相當于 動態數組。與Java中的數組相比,它的容量能動態增長。它繼承于AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。
AbstractList、List提供了添加、刪除、修改、遍歷等功能。
RandmoAccess提供了隨機訪問功能
Cloneable提供了可以被克隆的功能
Serializable提供了序列化的功能
和Vector不同,ArrayList中的操作不是線程安全的!所以,建議在單線程中才使用ArrayList,而在多線程中可以選擇Vector或CopyOnWriteArrayList。
◆
ArrayList的屬性
◆
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /** * 數組默認的大小 */ private static final int DEFAULT_CAPACITY = 10; /** * 使用數組大小為0時的默認緩沖區 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 使用ArrayList(int initialCapacity)構造方法時且initialCapacity為0時緩沖區 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 真實存儲arraylist元素的數組緩沖區 */ transient Object[] elementData; // non-private to simplify nested class access /** * List的實際大小 */ private int size; /** * 數組可分配的最大大小 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * ?特別注意這個是繼承自AbstractList的屬性,用來記錄List被修改的次數 */ protected transient int modCount = 0; |
◆
ArrayList的構造方法
◆
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /** * 無參構造方法,初始化elementData */ public ArrayList() { ? ?this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 根據參數構建具有初始大小的構造方法 */ 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); ? ?} } /** * 創建一個包含collection的ArrayList */ public ArrayList(Collection<? extends E> c) { ? ?elementData = c.toArray(); ? ?if ((size = elementData.length) != 0) { ? ? ? ?// c.toArray might (incorrectly) not return Object[] (see 6260652) ? ? ? ?if (elementData.getClass() != Object[].class) ? ? ? ? ? ?elementData = Arrays.copyOf(elementData, size, Object[].class); ? ?} else { ? ? ? ?// replace with empty array. ? ? ? ?this.elementData = EMPTY_ELEMENTDATA; ? ?} } |
◆
ArrayList的方法
◆
接下來我們就以ArrayList的幾個比較經典的方法來看一下它是如何設計的。
首先是添加方法,添加的方法一共有3個:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | /** ? ?* 添加元素 ? ?*/ ? public boolean add(E e) { ? ? ? //計算數組最新的容量,以及判斷是否需要擴容 ? ? ? ensureCapacityInternal(size + 1); ?// Increments modCount!! ? ? ? elementData[size++] = e; ? ? ? return true; ? } ? /** ? ?* 指定索引添加元素 ? ?*/ ? 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; ? ? ? //List長度+1 ? ? ? 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; ? } |
仔細觀察上方三個添加的方法,它們都調用了ensureCapacityInternal方法,這個方法的參數是執行當前添加操作所需要的數組容量。它會根據傳遞的參數來計算數組是否需要擴容,如果需要擴容則完成擴容操作。
不同之處在于,上方的兩個方法添加的只有一個元素,所以傳的size+1,而addAll因為是添加的一個集合所以傳的參數是size+集合的長度。
接著看這個方法的實現:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /** ? ?* 計算數組最新的容量 ? ?* @param minCapacity ? ?*/ ? private void ensureCapacityInternal(int minCapacity) { ? ? ? //如果創建ArrayList時指定大小為0 ? ? ? if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { ? ? ? ? ? //如果本次添加的大小比初始容量10大的話則不使用默認的容量10,直接使用本次添加的大小作為初始容量 ? ? ? ? ? minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); ? ? ? } ? ? ? ensureExplicitCapacity(minCapacity); ? } ? /** ? ?* 記錄修改次數,調用擴容方法 ? ?* @param minCapacity ? ?*/ ? private void ensureExplicitCapacity(int minCapacity) { ? ? ? modCount++; ? ? ? // overflow-conscious code ? ? ? if (minCapacity - elementData.length > 0) ? ? ? ? ? //擴容 ? ? ? ? ? grow(minCapacity); ? } ? /** ? ?* 擴容 ? ?*/ ? private void grow(int minCapacity) { ? ? ? // 獲取原來的數組長度 ? ? ? int oldCapacity = elementData.length; ? ? ? //新容量設置為老容量的1.5倍 ? ? ? int newCapacity = oldCapacity + (oldCapacity >> 1); ? ? ? //如果新容量還不夠存放本次需要添加的大小,則直接擴容到本次添加的大小 ? ? ? if (newCapacity - minCapacity < 0) ? ? ? ? ? newCapacity = minCapacity; ? ? ? //如果新容量超出數組最大容量 ? ? ? if (newCapacity - MAX_ARRAY_SIZE > 0) ? ? ? ? ? newCapacity = hugeCapacity(minCapacity); ? ? ? // 調用Arrays的復制方法更新數據緩沖池 ? ? ? 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; ? } |
以上就是ArrayList動態擴容的實現方式了,這里注意一下擴容是通過新建一個數組來替換原先的數組來進行的:
| 1 | elementData = Arrays.copyOf(elementData, newCapacity); |
接下來看刪除操作:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /** ? ?* 遍歷數組,找出需要刪除的元素的索引,并調用刪除方法 ? ?*/ ? 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; ? } /** ? ?* 刪除指定索引的元素 ? ?* ? ?*/ ? 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; ? } ? /* ? ?* 刪除指定元素 ? ?*/ ? 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 ? } |
需要注意的是刪除一個元素也是通過底層的方法實現的。
接著看get和set相對就比較簡單了。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public E get(int index) { ? ? ? //判斷索引是否越界 ? ? ? rangeCheck(index); ? ? ? return elementData(index); ? } ? ?/** ? ?* 判斷索引是否越界 ? ?*/ ? private void rangeCheck(int index) { ? ? ? if (index >= size) ? ? ? ? ? throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); ? } ? public E set(int index, E element) { ? ? ? //判斷索引是否越界 ? ? ? rangeCheck(index); ? ? ? //獲取此索引原先的值 ? ? ? E oldValue = elementData(index); ? ? ? elementData[index] = element; ? ? ? return oldValue; ? } |
看了ArrayList的增刪改查方法相信你已經明白了為什么一直有人告訴你ArrayList查詢修改效率高而添加和刪除效率低了。
ArrayList的序列化方式同樣是比較有意思的,一開始看到ArrayList實現了Serializable我們就知道它是可以序列化的,但是實際存儲的數組elementData卻是transient,觀看下方代碼你就可以找到答案:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | /** ? * 將List寫入s,注意先寫容量,然后在寫數據 ? * @param s ? * @throws java.io.IOException ? */ ?private void writeObject(java.io.ObjectOutputStream s) ? ? ? ? ?throws java.io.IOException{ ? ? ?// Write out element count, and any hidden stuff ? ? ?int expectedModCount = modCount; ? ? ?s.defaultWriteObject(); ? ? ?// 首先寫數組容量 ? ? ?s.writeInt(size); ? ? ?// 遍歷寫數組中的元素 ? ? ?for (int i=0; i<size; i++) { ? ? ? ? ?s.writeObject(elementData[i]); ? ? ?} ? ? ?if (modCount != expectedModCount) { ? ? ? ? ?throw new ConcurrentModificationException(); ? ? ?} ?} ?/** ? * 讀取s中的List ? */ ?private void readObject(java.io.ObjectInputStream s) ? ? ? ? ?throws java.io.IOException, ClassNotFoundException { ? ? ?elementData = EMPTY_ELEMENTDATA; ? ? ?// Read in size, and any hidden stuff ? ? ?s.defaultReadObject(); ? ? ?// 首先讀數組容量 ? ? ?s.readInt(); // ignored ? ? ?if (size > 0) { ? ? ? ? ?// be like clone(), allocate array based upon size not capacity ? ? ? ? ?ensureCapacityInternal(size); ? ? ? ? ?Object[] a = elementData; ? ? ? ? ?// Read in all elements in the proper order. ? ? ? ? ?for (int i=0; i<size; i++) { ? ? ? ? ? ? ?a[i] = s.readObject(); ? ? ? ? ?} ? ? ?} ?} |
鑒于篇幅有限,本篇文章僅列出上方部分代碼,ArrayList完整源碼解析請看:https://github.com/shiyujun/syj-study-demo!!!
博客所有文章首發于公眾號《Java學習錄》轉載請保留
掃碼關注公眾號即可領取2000GJava學習資源
轉載于:https://blog.51cto.com/12980017/2372727
總結
以上是生活随笔為你收集整理的Java集合-ArrayList源码解析-JDK1.8的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vivo9.0系统设备最简单激活XPOS
- 下一篇: SpringBoot JPA不调用sav