本篇文章主要是學習後的知識記錄,存在不足,或許不夠深入,還請諒解。
ArrayList源碼解析
ArrayList源碼解析
ArrayList中的變量
通過上圖可以看到,ArrayList中總共有7個變量,下面看下每個變量的作用:
/**
* 序列化
*/
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默認的初始容量
*/
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
/**
* size,是用來記錄arrayList中,即elementData裏的元素的大小,數組中共有多少個元素
* @serial
*/
private int size;
/**
* 這個參數,是數組所允許的最大長度,要是超出了,可能會報OutOfMemoryError異常
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
ArrayList構造函數
ArrayList中有三個構造函數:
/**
* 指定一個具有初始容量的空數組,可以自己指定容量的大小
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException 指定的初始容量值若是負值,會拋出這個異常
*/
public ArrayList(int initialCapacity) {
//容量大於0,就把elementData初始化一個具有initialCapacity大小的數組
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//要是指定的容量值是0,就初始化一個空數組
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//指定的容量值小於0.拋出不合法參數異常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 無參構造函數,會指定一個初始容量爲10的數組
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 構造一個包含指定元素的列表集合,按照集合返回它們的順序
*
* @param c 傳入的集合,會把集合中的元素放入數組中
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//數組中元素的長度不爲0,就把c的元素copy到elementData中
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 {
//要是元素個數是0個,就初始化一個空數組
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList中的add方法
/**
* add方法,會把一個元素添加到數組的末尾
*
* @param e 傳入的元素
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//size是目前數組存的元素的個數,傳入size+1,即需要的數組長度
//需要的數組長度,是可以再去容納一個元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//這裏傳入的是size+1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//這個方法是判斷數組是否是一個空數組,要是使用無參構造函數初始化的arrayList,那麼返回值就是10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//要是elementData是一個空的數組
//判斷需要的數組長度和默認容量值哪個大,返回最大的那個
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//要是elementData數組中有元素,直接返回minCapacity
return minCapacity;
}
//這裏是,判斷數組是否需要擴容
private void ensureExplicitCapacity(int minCapacity) {
//記錄數組被修改的次數
modCount++;
//要是需要的數組長度大於目前數組的長度,就需要擴容了(即數組的長度是否可以存入下一個元素)
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 擴容方法
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
//舊的數組的長度
int oldCapacity = elementData.length;
//需要擴容的數組的長度,即10*1.5=15
int newCapacity = oldCapacity + (oldCapacity >> 1);
//要是擴容後的數組的長度還是小於需要的最小容量,那麼就把需要的最小容量給newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//要是擴容後的數組長度比最大的數組容量還大,就需要控制了
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//把擴容之後的數組copy到長度爲newCapacity的數組中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//參數小於0.拋異常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//判斷需要的數組容量和數組最大容量哪個大,
//需要的數組容量比數組最大容量還大,就返回int的最大值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
圖式:
ArrayList中的add(插入指定位置)方法
先看下 System.arraycopy:
// src 源數組
// srcPos 源數組要複製的起始位置
// dest 要賦值到的目標數組
// destPos 目標數組放置的起始位置
// length 複製的長度
System.arraycopy(Object src, int srcPos, Object dest, int destPos,int length);
繼續add方法:
/**
* 將元素插入數組中指定的位置
* @param index 指定的索引值
* @param element 需要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//參數校驗,判斷要插入的下標是否越界
rangeCheckForAdd(index);
//這個和add(E e)是一樣的,判斷數組是否需要擴容等
ensureCapacityInternal(size + 1); // Increments modCount!!
//把插入index位置的原有元素以及該元素後面的元素,向右移動
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//把元素插入到index的位置
elementData[index] = element;
//數組元素的個數加1
size++;
}
/**
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheckForAdd(int index) {
//要是插入的位置比數組中元素的個數大,或者插入的位置值小於0,就拋出下標越界異常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Constructs an IndexOutOfBoundsException detail message.
* Of the many possible refactorings of the error handling code,
* this "outlining" performs best with both server and client VMs.
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
ArrayList中的get方法
get方法相對而言就比較簡單些:
/**
* 根據下標獲取指定位置的元素
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
//參數校驗,若index比數組中元素的size還大,就拋異常
rangeCheck(index);
//返回對應的元素
return elementData(index);
}
/**
* 在獲取數組元素之前,需要進行數據校驗
* 若傳入的參數不在指定的數組索引範圍內,就拋異常
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
ArrayList中的remove(int index)方法
/**
* 移除列表中指定位置的元素,然後把移除元素後面的元素向左移動
*
* @param index 需要移除元素的索引值
* @return 移除的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//參數校驗,校驗index的值是否在索引所允許的範圍內
rangeCheck(index);
//列表的修改次數加一
modCount++;
//先查出對應index位置出的元素,賦值給oldValue
E oldValue = elementData(index);
//把移除的元素後面的所有元素向左移動
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//把數組最後一個索引位置數據設置爲null
elementData[--size] = null; // clear to let GC do its work
//返回移除的元素數據
return oldValue;
}
ArrayList中的remove(Object o)方法
/**
*
* 從列表中刪除指定元素的第一個匹配項
*
* @param o 需要移除的元素
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
//若元素是null,移除第一個匹配爲null的元素
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
//若元素不爲null,就移除第一個匹配到的元素
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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
}
注意:由於ArrayList是線程不安全的,所以不要在遍歷中去對ArrayList做修改,否則會出現錯誤
ArrayList中的clear方法
/**
* 清除列表中的所有元素
*/
public void clear() {
//列表修改次數加一
modCount++;
// clear to let GC do its work 清除所有元素,垃圾回收
//通過遍歷把所有元素設置爲Null
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
CopyOnWriteArrayList源碼解析
通過上圖可以看到copyOnWrite的實現方式,這種方式適用於讀極多,寫極少的情況,而且如果數據量巨大,在copy之後的一瞬間,內存佔用增加,也會引發問題。CopyOnWriteArrayList是線程安全的。
CopyOnWriteArrayList變量
/** 可重入鎖 */
final transient ReentrantLock lock = new ReentrantLock();
/**
* 數組,只能通過getArray和setArray操作
*/
private transient volatile Object[] array;
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
CopyOnWriteArrayList的構造函數
/**
* 創建一個空的列表
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* 構造一個包含指定元素的列表集合,按照集合返回它們的順序
*
* @param c 傳入的集合
* @throws NullPointerException if the specified collection is null
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
/**
* 創建包含給定數組副本的列表
*
* @param toCopyIn the array (a copy of this array is used as the
* internal array)
* @throws NullPointerException if the specified array is null
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
CopyOnWriteArrayList的兩個add方法
/**
* 添加一個元素到列表的最後面
*
* @param e 需要添加到列表中的元素
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//初始化鎖
final ReentrantLock lock = this.lock;
//加鎖
lock.lock();
try {
//獲取數組中的元素
Object[] elements = getArray();
//獲取數組的長度
int len = elements.length;
//把elements數組copy到長度爲len + 1的newElements數組中,即新的數組長度增加1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//然後把元素加到數組的末尾
newElements[len] = e;
//set數組的元素爲添加之後的數組
setArray(newElements);
return true;
} finally {
//解鎖
lock.unlock();
}
}
/**
* 向指定的索引位置加入元素,加入位置後面的元素需要向右移位
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//初始化一個鎖
final ReentrantLock lock = this.lock;
//加鎖
lock.lock();
try {
//獲取數組中的元素
Object[] elements = getArray();
//獲取數組的長度
int len = elements.length;
//如果傳入的索引值不在數組所允許的範圍內,就拋異常
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
//如果插入的索引大小和數組長度一樣,那麼直接插入到數組末尾
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
//設置新數組的長度比之前的數組長度大一位,即可以插入一個元素
newElements = new Object[len + 1];
//先copy elements中index索引之前的元素到newElements
System.arraycopy(elements, 0, newElements, 0, index);
//再把elements中index之後的元素已經index中的元素copy到index右邊,即右移
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//把元素放入到指定的索引處
newElements[index] = element;
//設置數組的引用爲新的數組
setArray(newElements);
} finally {
//解鎖
lock.unlock();
}
}
向指定索引處插入元素圖解:
CopyOnWriteArrayList的兩個get方法
get方法比較簡單,不做贅述。
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
CopyOnWriteArrayList的remove方法
/**
* 移除列表中指定索引位置的元素,並把後續的元素向左移動
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//初始化鎖
final ReentrantLock lock = this.lock;
//加鎖
lock.lock();
try {
//獲取數組中的元素
Object[] elements = getArray();
//獲取數組的長度
int len = elements.length;
//獲取索引處原先的舊值
E oldValue = get(elements, index);
int numMoved = len - index - 1;
//如果要移除的是最後一位直接移除
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//新的數組,長度比舊的數組少一位
Object[] newElements = new Object[len - 1];
//同樣的,//先copy elements中index索引之前的元素到newElements
System.arraycopy(elements, 0, newElements, 0, index);
//再把elements中index+1之後的元素向左移位
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
//返回移除後的元素
return oldValue;
} finally {
//解鎖
lock.unlock();
}
}
在CopyOnWriteArrayList,移除的方法還有另外兩個,實現的方法也都大同小異,都是先copy一份列表,然後加鎖去操作,移除掉元素,然後再把數組的引用指向移除後的數組即可。
到此,arrayList和CopyOnWriteArrayList源碼就結束了,上面的解釋以及註釋可能有錯誤或者不足的地方,希望指正,共同進步,多謝!