寫在前面
ArrayList相信大家做開發的同學都不陌生,在開發過程中這應該是最常用的數據結構了吧。但是現在是“源碼時代”,會用還不夠,要知道他的實現原理,本文主要基於jdk1.8對ArrayList源碼進行分析。
一、從主要字段開始
值得注意的是,ArrayList內部會有一個modCount字段,但是這個字段是在父類AbstractList中的,代表着修改次數,後面會講
/**
* 默認容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空元素集,構造函數傳入空集合的時候使用
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 空元素集,默認無參構造函數中使用
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
ArrayList真正保存元素的數組,在第一次插入元素的時候擴容
*
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 當前數組元素個數
*
* @serial
*/
private int size;
接下來看一下主要的構造方法:
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
//這個是帶數組容量的構造函數
public ArrayList(int initialCapacity) {
//如果初始容量大於0,則初始化底層元素數組
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果等於0,則將上面的空數組賦值
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果是小於0的負數,拋非法參數異常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
//默認無參構造給的是一個空數組,不在這個時候擴容
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
//傳入集合的構造函數
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//如果長度不爲0,進此邏輯
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//如果該元素數組類型不是等於Object[],則拷貝一份Object[]類型的數組 並賦值給
//elementData
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//長度是0,給空數組
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
二、add方法
1、list默認的add方法:
public boolean add(E e)
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//好這邊可以看到,在添加元素前搞了一個這個不可描述的動作,從之前的構造方法我們可以看出,
//一開始數組根本沒有初始化,所以擴容動作必定在這個函數裏做的
ensureCapacityInternal(size + 1); // Increments modCount!!
//這裏添加元素
elementData[size++] = e;
return true;
}
來看一下那個函數
private void ensureCapacityInternal(int minCapacity) {
//調用了這個函數,記住這個minCapacity代表啥:size+1
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
再來看一下參數裏面這個calculateCapacity函數
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果元素數組爲空數組
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//返回默認容量和size+1中較大的那個
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果不是空數組,返回size+1
return minCapacity;
}
繼續跟蹤:
private void ensureExplicitCapacity(int minCapacity) {
//首先modCount加1,這個是幹嘛用的後面會說
modCount++;
// overflow-conscious code
//關鍵點來了,這句話啥意思?size+1比當前數組容量大的時候,很明顯剛開始是0,1比0大很正常
if (minCapacity - elementData.length > 0)
//grow方法明顯是擴容的真正方法
grow(minCapacity);
}
繼續來看grow函數:
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
//獲取數組原始容量
int oldCapacity = elementData.length;
//注意這行,擴容後的新容量等於老容量右移一位加上自身,也就是原來的1.5倍,而不是hashmap的
//兩倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量還是比size+1小,這是什麼情況?想想
//當然是老容量爲0的時候,0*1.5=0,所以這個時候新容量等於1
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//當擴容之後的新容量比這個整形最大值-8還要大的時候
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);
}
來看一下上面的hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
//如果size+1之後比0小,也就是整形溢出的時候,直接拋異常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//否則如果比整形最大值-8大 就取整形最大值Integer.MAX_VALUE,否則取整形最大值-8
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
至此整個擴容操作完成。
2、指定位置添加元素add方法
public void add(int index, E element)
public void add(int index, E element) {
//這個函數很簡單主要是檢查index下標是否越界,否則拋異常
rangeCheckForAdd(index);
//這個函數上面分析過了,不再解釋
ensureCapacityInternal(size + 1); // Increments modCount!!
//這個函數可能大家沒見過,他的作用是將elementData數組的index位置開始size -index長度的
//元素全部拷貝到elementData 數組index+1的位置,也就是將index位置開始的數組全部後移一
//位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//再將插入的元素賦值給index位置
elementData[index] = element;
//數組元素加1
size++;
}
三、remove方法
1.根據下標刪除的remove方法
public E remove(int index) {
//檢查下標是否越界
rangeCheck(index);
//修改次數加1
modCount++;
E oldValue = elementData(index);
//需要移動多少個元素 如數組有5個元素 0,1,2,3,4,index等於2就是5-2-1=2,需要移動兩
//個元素
int numMoved = size - index - 1;
//如果移動的元素個數大於0
if (numMoved > 0)
//這個函數之前介紹過,之前是移動index之後的元素到index+1,現在恰好相反,把index+1
//後面的元素移動到index
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//把最後一個元素置爲null,方便jvm回收,也是上面如果移動的元素個數等於0 ,也就是要刪除的
//是最後一個元素時,直接置爲空
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
2.根據元素刪除的remove方法
public boolean remove(Object o) {
//如果元素等於null,則進入此邏輯
if (o == null) {
//遍歷數組
for (int index = 0; index < size; index++)
//如果遇到元素爲null的
if (elementData[index] == null) {
//快速刪除,這個函數下面會講
fastRemove(index);
return true;
}
} else {
//如果要刪除的元素不等於null
for (int index = 0; index < size; index++)
//遍歷過程遇到相等的,則刪除
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
來看一下fastRemove方法:
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
//你發現了什麼?哎好像就是走之前根據索引刪除的邏輯 一摸一樣啊
//但是仔細看 你會發現少了一個下標越界的檢查操作,看頭頂這行註釋,爲什麼?該方法是私有方法
//調用的地方已經保證index在0到size-1之間,所以這個方法名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
}
三、迭代操作
我不知道你面試的時候,有沒有被問到過,ArrayList在循環的時候,能刪除元素嗎?如果你看過面試題,肯定知道答案是不行,那我們來看看爲啥不行。這就和上面頻繁出現的modCount有關了。
首先我們要知道ArrayList有iterator()方法,這個方法的來源是Collection接口繼承了Iterable接口,實現這個接口說明該類是可迭代的,而iterator方法的返回值 類型Iterator成爲迭代器,是需要自己實現的,這個類似於Comparable和Comparator的關係。
知道了這個,那我們對增強for循環一定不陌生,那憑什麼ArrayList可以用增強 for循環,我們自己的類卻不行?原因是增強for底層用的是迭代器操作,我們只要自己實現Iterable接口,也能使用增強for。
public interface Collection<E> extends Iterable<E>
public Iterator<E> iterator() {
return new Itr();
}
接下來看一下問題關鍵:ArrayList的迭代器實現
private class Itr implements Iterator<E> {
//遊標,代表的是當前元素的索引下標
int cursor; // index of next element to return
//最近返回的元素
int lastRet = -1; // index of last element returned; -1 if no such
//期待的修改次數,我們發現他在迭代器被創建的時候就已經被指定了
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
//遊標是否已到最大值
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//這是關鍵所在,檢查修改次數,不一樣會拋異常,下面方法寫了,而上面我們看到無論是添
//加元素還是刪除元素modCount都會加1因此過不了這裏的校驗
checkForComodification();
int i = cursor;
//如果遊標大於數組長度,拋出異常
if (i >= size)
throw new NoSuchElementException();
//這裏可能大家會有疑問,上面不是已經判斷遊標是否越界了嗎怎麼還要判斷
//其實這邊應該是爲了防止多線程併發修改的情況。比如{1,2,3,4,5}有五個元素,這時候線程
//1在遍歷,遊標 剛好在索引4這個位置,線程2此時刪除了一個元素,這個時候數組長度只有4
//了,此時顯然取不到那個元素,因此此處直接拋出異常
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//遊標+1
cursor = i + 1;
//獲取對應位置的元素,並賦值給lastRet
return (E) elementData[lastRet = i];
}
//這是迭代器自己的刪除方法,在迭代中是可用的,我們來看下是爲什麼
public void remove() {
// 如果一次都沒迭代過,則拋異常,只要調用了next,lastRet都會有值
if (lastRet < 0)
throw new IllegalStateException();
//檢查狀態
checkForComodification();
try {
//調用arraylist自己的remove方法,哎?那按理說modCount++會拋異常啊,爲啥沒有
//來看下面代碼
ArrayList.this.remove(lastRet);
//next之後遊標會加1,但是我們刪除了元素,所以把遊標重置到當前位置
cursor = lastRet;
//這裏是重置lastRet爲-1,刪除之後必須迭代到下一個
lastRet = -1;
//這行代碼你發現了什麼?modCount又賦值給了expectedModCount,所以不會報錯
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
//如果當前的修改次數不等於期待的修改次數,拋出異常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
四、寫在最後
關於ArrayList源碼的解析就先寫到這裏,原創不易,註釋都是一行一行手敲的,轉載請註明出處。歡迎大家進行評論和點贊,有什麼問題也可以加我私人微信:zyj-jy66。