在平時Java
,儲存資料需要用到列表,而大多時候都能用到ArrayList
,比如Mybatis
查詢資料列表,返回列表都是ArrayList
,很多資料的存放也用到了ArrayList
。
jdk 版本: 1.8
ArrayList
是基於大小可變的陣列實現,並允許新增null
值, 根據下標就能資料查詢快。陣列一旦初始化就確定好了大小,如果需要新增資料,就需要做資料的複製,這些操作比較耗時。
陣列拷貝
ArrayList
陣列的擴容、新增和刪除需要用到陣列的拷貝,主要用到了以下兩個方法:
System.arraycopy
Arrays.copyOf
System.arraycopy
System.arraycopy()
是Java
的原生的靜態方法,該方法將源陣列元素拷貝到目標陣列中去。
引數詳解:
- src 源陣列
- srcPos 源陣列拷貝的起始索引
- dest 目標陣列
- destPos 拷貝到目標陣列的起始索引
- length 拷貝元素的個數
將src
源陣列,起始索引為srcPos
,個數為length
,複製到起始索引為destPos
的dest
目標陣列。
比如:
int[] srcArray = new int[]{1,2,3,4,5,6};
int[] desArray = new int[5];
System.arraycopy(srcArray,1,desArray,2,3);
System.out.println("desArray: " + Arrays.toString(desArray));
輸出:
[0, 0, 2, 3, 4]
Arrays.copyOf
Arrays.copyOf
本質也是呼叫System.arraycopy
方法:
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
首先建立一個新的陣列copy
,將original
陣列全部複製給copy
陣列。
主要欄位:
// 底層陣列
transient Object[] elementData;
// 陣列個數大小
private int size;
// 預設空陣列
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
ArrayList
是基於陣列的一個實現,elementData
就是底層的陣列。size
是記錄陣列的個數。
構造方法
ArrayList()
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
無參構造方法,建立資料直接賦值一個空陣列。
ArrayList(int initialCapacity)
賦一個初始化容量initialCapacity
:
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);
}
}
- 初始化容量
initialCapacity
大於零,新建長度initialCapacity
的陣列。 - 初始化容量
initialCapacity
等於零,新建一個空陣列。
新增資料
新增資料有兩個方法:
- add(E e) 直接新增在陣列尾部。
- add(int index, E element) 根據下標新增資料。
add(E e)
在列表尾部新增資料:
/**
* Appends the specified element to the end of this list.
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal
判斷新增資料後的容量是否足夠,如果不夠,做擴容操作。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
ensureCapacityInternal
作用是判斷容量當前容量是否足夠存放新的資料,如果不夠就做擴容操作。
calculateCapacity
計算容量,如果陣列為null
,返回minCapacity
和10
的最大值,否則返回minCapacity
。這個方法就是返回陣列的大小。
呼叫無參構造方法
ArrayList()
,再呼叫add()
方法,首先陣列容量會變成10
。這也是為什麼無參構造方法ArrayList()
上面有會有註解Constructs an empty list with an initial capacity of ten.
ensureExplicitCapacity
判斷需要的容量minCapacity
大於當前陣列的長度elementData.length
,將會做擴容處理,也是呼叫grow
方法:
private void grow(int minCapacity) {
// overflow-conscious code
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);
// 拷貝陣列到新的長度陣列
elementData = Arrays.copyOf(elementData, newCapacity);
}
group
主要做了兩件事:
- 長度擴大到原來的 1.5倍
- 拷貝陣列到新長度的陣列
做完判斷是否要做擴容之後,直接在size位置上賦值要新增的元素,然後size再自增。
elementData[size++] = e;
add(E e)流程總結
- 判斷資料容量是否足夠,如果是空陣列,返回
10
,其他容量返回當前陣列size + 1
。 - 返回容量和當前陣列長度對比,小於做擴容處理。
- 擴容長度為原來長度的
1.5倍
,將陣列賦值給長度為原來陣列1.5
倍的新陣列。 - 在陣列的最末尾
size
賦值。
add(int index, E element)
此新增資料是在指定的下標新增資料。
public void add(int index, E element) {
// 下標是否越界
rangeCheckForAdd(index);
// 判斷是否要擴容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 複製i到size的資料到i+1 到size +1
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
add(int index, E element) 在index
下標新增資料,
- 首先判斷
index
是否在0 ~size
之間。 - 判斷是否要擴容,需要擴容,進行
1.5
倍擴容。 - 資料遷移,把
index
以後的資料全部往後移動一位。 - 賦值
index
下標資料。
獲取資料
獲取資料只有通過陣列下標獲取資料 get(int index)
:
public E get(int index) {
//檢查是否超過陣列範圍
rangeCheck(index);
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
// 通過下標獲取資料
return (E) elementData[index];
}
這裡獲取資料就比較簡單:
- 檢查下標是否超過陣列範圍。
- 通過下標訪問資料。
刪除資料
remove(Object o)
刪除列表中第一個匹配的元素:
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;
}
判斷要刪除資料是否為null
:
- 為空,判斷
elementData[index]
是否為空。 - 不為空,判斷元素
equals
是否匹配elementData[index]
。
上面判斷都為true
時,呼叫fastRemove
方法:
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
}
移動陣列資料,index+1
以及之後的資料往前移動一位,最後一位size-1
賦值為null
。
remove(int index)
根據index
下標刪除資料。
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
方法,System.arraycopy
是將src
源陣列,起始索引為srcPos
,個數為length
,複製到起始索引為destPos
的dest
目標陣列。 ArrayList
主要是基於Object[] elementData
動態陣列實現。- 呼叫構造方法,無參的就賦值一個空陣列,有初始容量的就賦值一個初始容量。
add
新增陣列首先判斷是否需要擴容,需要擴容,長度擴大成原來的1.5
倍。- add(E e) 在
size
賦值新增資料 - add(int index, E element) 將陣列從
index
往後移動一位,新資料新增到index
上。
- add(E e) 在
- 獲取資料
get(int index)
利用陣列的特定,根據下標獲取資料。 remove(int index)
將index
之後的資料全部往前移動一位,size - 1
賦為null
。