說(shuō)明一下,本次源碼是基于jdk1.8的厂榛。
我在了解寺渗,學(xué)習(xí)一個(gè)東西的時(shí)候,首先要了解清楚跟它有關(guān)的都有哪些東西扭倾,這些東西之間有著怎樣的關(guān)聯(lián)、結(jié)構(gòu)挽绩。所以膛壹,我會(huì)首先選擇從大處著眼,理清整個(gè)集合的層次關(guān)系唉堪,以及ArrayList它在整個(gè)集合的位置模聋。
從ArrayList這個(gè)類開(kāi)始,一層一層向上找唠亚,終于經(jīng)過(guò)一番不太愉快的努力之后撬槽,終于做出了一副圖(我就先將就著看吧)
從這幅奇丑無(wú)比的圖中,我們可以忍著痛暫時(shí)看出ArrayList集合在Collection這個(gè)框架中的一個(gè)層次趾撵。
接下來(lái)我要開(kāi)始庖丁解牛了侄柔,雖然刀法不怎么樣。
ArrayList的聲明:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
從聲明中占调,可以看到有泛型的存在暂题。泛型有一種較為正式的稱呼“參數(shù)化類型”,從“參數(shù)化”這個(gè)字面上去聯(lián)想究珊,就比較容易理解這個(gè)概念了薪者。一個(gè)函數(shù)聲明了它的調(diào)用參數(shù)之后,只要還沒(méi)有對(duì)這個(gè)函數(shù)進(jìn)行調(diào)用剿涮,那么它的參數(shù)的值也就沒(méi)有確定下來(lái)言津,只有當(dāng)調(diào)用了該函數(shù),傳遞了相應(yīng)的參數(shù)取试,其值才確定下來(lái)悬槽。以此類比,只有當(dāng)E的具體數(shù)據(jù)類型給定了瞬浓,這個(gè)泛型的類型才是真正確定了初婆。
看到泛型,我突然想到了在JVM中有一種叫“語(yǔ)法糖”的東西了猿棉。它不是糖磅叛,而且無(wú)色無(wú)味無(wú)實(shí)物,好吧其實(shí)就是一種語(yǔ)法萨赁。當(dāng)在java語(yǔ)言中使用了這種糖弊琴,在編譯期,會(huì)伴隨有類型擦除的動(dòng)作杖爽,即JVM會(huì)把當(dāng)初傳遞的類型參數(shù)給擦除掉敲董,再次變成Object(噢~紫皇,Object大法好!)臣缀,而在C++語(yǔ)言中則不會(huì)發(fā)生擦除動(dòng)作坝橡。所以相比來(lái)說(shuō),Java中的這種“語(yǔ)法糖”算是比較奇葩的精置。
不過(guò)話說(shuō)回來(lái)了计寇,既然最后還要把類型給擦除掉,可這種糖有什么用呢脂倦?肯定有用胺!那就是喂你吃赖阻,讓你開(kāi)心呀蝶押!什么意思?當(dāng)我們傳入了一個(gè)特定的參數(shù)類型后火欧,那么我們便可以放心地去向這個(gè)集合里面添加我們所需的元素棋电,即使失誤,添加了不對(duì)應(yīng)的類型苇侵,編譯器也會(huì)及時(shí)提醒我去做更改赶盔。反之,如果不傳入一個(gè)特定的類型參數(shù)榆浓,那么當(dāng)我們?cè)诰幊涕_(kāi)發(fā)的時(shí)候于未,什么時(shí)候腦子一熱,心里一興奮陡鹃,就把一個(gè)Integer烘浦,或者一個(gè)自定義類型的數(shù)據(jù)給放入進(jìn)去了。那么當(dāng)我們?cè)诒闅v該集合萍鲸,進(jìn)行類型轉(zhuǎn)換的時(shí)候闷叉,肯定是要出錯(cuò)的呀!同學(xué)們猿推,這個(gè)送分題可不能錯(cuò)捌啊!(嚴(yán)肅臉)
好蹬叭,回歸正題。接下來(lái)看一下ArrayList的屬性有哪些
private static final long serialVersionUID = 8683452581122892189L;
每一個(gè)可序列化的對(duì)象都要有這么一個(gè)serialVersionUID屬性值的状知。
private static final int DEFAULT_CAPACITY = 10;
ArrayList默認(rèn)的容量大小為10秽五!同學(xué)們,記住是10饥悴,又一道送分題坦喘!
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
接下來(lái)找一找ArrayList的構(gòu)造函數(shù)有哪些
//將elementData初始化為一個(gè)空的Object[]
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
空構(gòu)造函數(shù)盲再,ArrayList默認(rèn)容量為10;
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//如果initialCapacity大于0瓣铣,則對(duì)Object[]類型的elementData進(jìn)行初始化
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果initialCapacity等于0答朋,則默認(rèn)初始化一個(gè)空的Object[]
this.elementData = EMPTY_ELEMENTDATA;
} else {
//否則拋錯(cuò)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
指定ArrayList容量:initialCapacity
public ArrayList(Collection<? extends E> c) {
//將Collection數(shù)組轉(zhuǎn)化為Object[]
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[](see 6260652)
if (elementData.getClass() != Object[].class)
//如果轉(zhuǎn)化的對(duì)象數(shù)組不是Object[]類型的,就利用Arryas工具類的copyOf方法來(lái)進(jìn)行一個(gè)強(qiáng)制轉(zhuǎn)化
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
傳入的參數(shù)為Collection<? extends E>類型棠笑,相當(dāng)于直接將Collection集合中的一部分?jǐn)?shù)據(jù)直接copy到了剛剛實(shí)例化的ArrayList集合中梦碗。注意工具類:Arrays的使用。
讀完了構(gòu)造函數(shù)蓖救,接下來(lái)再來(lái)看普通方法(遵循增刪改查的順序)
接下來(lái)說(shuō)一下ArrayList的添加對(duì)象方法
添加對(duì)象:
public boolean add(E e)
public void add(int index, E element)
public boolean addAll(Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c)
1.添加一個(gè)對(duì)象
public boolean add(E e) {
// Increments modCount!!
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
該方法就是向ArrayList集合的末尾添加一個(gè)對(duì)象洪规,即調(diào)用elementData[size++] = e,ArrayList的增刪改差實(shí)際就是通過(guò)內(nèi)部的這個(gè)Object類型的數(shù)組來(lái)實(shí)現(xiàn)的循捺,沒(méi)錯(cuò)就是這么簡(jiǎn)單斩例。不過(guò)在增加之前還要再進(jìn)行一次容量的判斷,即判斷增加之后的數(shù)組大小和原來(lái)的數(shù)組大小从橘,如果原來(lái)數(shù)組大小偏小了念赶,則要進(jìn)行動(dòng)態(tài)擴(kuò)充,擴(kuò)充的原則則是恰力,oldSize *3/2,即oldSize的3/2
public void add(int index, E element) {
rangeCheckForAdd(index);
// Increments modCount!!
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
這個(gè)方法同樣也是增加一個(gè)對(duì)象叉谜,不過(guò)這個(gè)在指定位置出增加一個(gè)對(duì)象,所以該位置后面的所有對(duì)象都需要向后移動(dòng)一個(gè)位置牺勾,那么怎樣才能做到呢正罢?那就需要System.arraycopy()來(lái)幫忙啦。
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
通過(guò)查看System類的源碼可對(duì)該方法有一個(gè)初步的了解了驻民。arraycopy為一個(gè)native方法翻具,所謂native方法是指首先該方法不是由Java來(lái)實(shí)現(xiàn)的,而是由其他偏底層的語(yǔ)言來(lái)實(shí)現(xiàn)的(比如C)回还,這樣通過(guò)調(diào)用這個(gè)方法裆泳,相比于非native方法來(lái)說(shuō),這類方法往往具有更高的效率柠硕。
這里有一篇英文文章來(lái)講解如何一步步來(lái)實(shí)現(xiàn)一個(gè)native method的
看完了native工禾,再來(lái)看一下五個(gè)參數(shù)。這個(gè)方法應(yīng)該是我到目前為止見(jiàn)到過(guò)參數(shù)最多的一個(gè)了蝗柔。通過(guò)參數(shù)的名字也不難理解它們各自的含義了闻葵,所以總的來(lái)說(shuō)這個(gè)方法的作用就是根據(jù)給定的一個(gè)數(shù)組和起始位置還有復(fù)制的長(zhǎng)度,來(lái)生成一個(gè)從指定位置復(fù)制后得到的對(duì)象癣丧。
另外再補(bǔ)充一個(gè)作用比較類似的方法:Arrays.copyof(T[] original, int newLength)
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
給定一個(gè)對(duì)象數(shù)組和長(zhǎng)度槽畔,將返回一個(gè)復(fù)制過(guò)的相應(yīng)長(zhǎng)度的對(duì)象數(shù)組
所以接著回上邊的add方法,經(jīng)過(guò)容量的檢測(cè)胁编,然后調(diào)用arraycopy方法來(lái)將原來(lái)index位置及其以后的元素全部增加一位重新復(fù)制到原來(lái)的數(shù)組中厢钧,最后在index位置處插入要增加的對(duì)象鳞尔。
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
// Increments modCount
ensureCapacityInternal(size + numNew);
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
該addAll()方法將一個(gè)Collection類型的集合直接添加到ArrayList的后邊。
其中要涉及判斷容量是否需要擴(kuò)充早直,復(fù)制擴(kuò)充原來(lái)的數(shù)組寥假。
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
// Increments modCount
ensureCapacityInternal(size + numNew);
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
該方法的作用是,在指定的位置處將一個(gè)Collection集合直接給插進(jìn)去霞扬。所以糕韧,既然有index索引位置參數(shù),所以要首先檢查是否訪問(wèn)越界祥得,檢測(cè)完正常之后兔沃,再去判斷是否要發(fā)生集合的擴(kuò)充,所以調(diào)用了ensureCapacityInternal()方法级及,最后再根據(jù)相應(yīng)的插入點(diǎn)位置來(lái)調(diào)用System.arraycopy方法來(lái)進(jìn)行一個(gè)數(shù)組的復(fù)制乒疏,即為插入之后的數(shù)組。
接下來(lái)說(shuō)一下ArrayList的刪除方法
刪除方法:
public E remove(int index)
public boolean remove(Object o)
private void fastRemove(int index)
public void clear()
第一個(gè)remove(int 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;
}
經(jīng)過(guò)了上面對(duì)arraycopy方法的分析饮焦,發(fā)現(xiàn)這個(gè)remove(int index)突然變得如此的簡(jiǎn)單怕吴,只需要注意這個(gè)rangeCheck(index)邊界檢查的方法。
不過(guò)县踢,還需要注意變量modCount的變化转绷,每當(dāng)有刪除操作時(shí),該值就會(huì)+1硼啤,那么有什么用呢议经?這個(gè)在fail-fast機(jī)制中起到了一個(gè)提醒危險(xiǎn)的作用,至于具體是什么作用呢谴返?我也不會(huì)煞肾,可是我當(dāng)然會(huì)去搞懂它呢!
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
如果index >= size,就會(huì)拋異常:IndexOutOfBoundsException
第二個(gè)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;
}
通過(guò)對(duì)集合進(jìn)行遍歷嗓袱,找到符合刪除對(duì)象的索引籍救,然后調(diào)用fastRemove(int index)方法來(lái)快速刪除該對(duì)象。
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
}
之所以叫做fastRemove渠抹,就是因?yàn)楫?dāng)他決定開(kāi)始刪除對(duì)象的時(shí)候蝙昙,就不用再進(jìn)行randCheck了,因?yàn)樵谕膺叺姆椒ㄖ幸呀?jīng)保證了index一定小于size梧却。
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
該方法的作用為刪除一個(gè)指定集合奇颠。不過(guò)在刪除之前首先會(huì)判斷該集合是否為空,即調(diào)用了為 final類型的Objects.requireNonNull(T obj),其源碼如下:
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
很簡(jiǎn)單的一段源碼放航,如果為空大刊,則拋NullPointerException()異常。接著再調(diào)用batchRemove方法三椿,其源碼如下:
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
這段代碼的作用就是批量刪除一個(gè)Collection集合對(duì)象缺菌。(好吧,我承認(rèn)沒(méi)怎么看懂)
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
調(diào)用clear()方法搜锰,簡(jiǎn)單粗暴地來(lái)清空整個(gè)集合伴郁。
接下來(lái)說(shuō)一下ArrayList的修改方法
修改方法:
public E set(int index, E element)
public void replaceAll(UnaryOperator<E> operator)
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
該set()方法首先也會(huì)檢查index是否越界,如果沒(méi)有越界蛋叼,直接將index位置的對(duì)象替換即可焊傅,最后返回值為原來(lái)位置的那個(gè)對(duì)象。
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
elementData[i] = operator.apply((E) elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
接下來(lái)說(shuō)一下ArrayList的查詢方法
修改方法:
public E get(int index)
public int indexOf(Object o)
public int lastIndexOf(Object o)
該方法的作用是替換一部分的對(duì)象狈涮,至于 UnaryOperator<E>
該類型之前沒(méi)有見(jiàn)過(guò)狐胎,沒(méi)有搞懂,所以這段代碼的分析暫時(shí)先擱置歌馍,隨后就來(lái)握巢,隨后就來(lái)
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
該方法傳入一個(gè)位置索引參數(shù),返回相應(yīng)的類型松却。不過(guò)暴浦,在return之前,還需要調(diào)用兩個(gè)方法rangeCheck(index)和elementData(index)晓锻,
看一下和elementData()方法歌焦,這是一個(gè)私有方法,所以肯定是在該類內(nèi)部進(jìn)行服務(wù)的砚哆,從源碼里可以很容易看出独撇,就是檢查一下索引位置是否越界。
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
從源碼上很容易看出是獲得相應(yīng)索引值的對(duì)象躁锁,而且由于前邊由于已經(jīng)對(duì)邊界進(jìn)行檢查過(guò)了纷铣,所以此處可以加一個(gè)注解@SuppressWarnings來(lái)提示JVM忽略該方法可能所產(chǎn)生的警告。
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;
}
該方法的作用同樣是查灿里,不過(guò)這次返回的是指定對(duì)象的索引值关炼,其實(shí)內(nèi)部的原理很簡(jiǎn)單,就是對(duì)該集合進(jìn)行一個(gè)遍歷匣吊,如果找到了指定的Object對(duì)象儒拂,就返回該索引的值。
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;
}
同上面方法的原理幾乎一樣色鸳,這次是倒序查找社痛。
一些其他常見(jiàn)適用方法
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
查詢是否包含指定對(duì)象
public boolean isEmpty() {
return size == 0;
}
該集合是否為空
public int size() {
return size;
}
返回該集合的大小
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
將一個(gè)arrayList對(duì)象轉(zhuǎn)換成Object對(duì)象數(shù)組
至此,我想已經(jīng)將ArrayList的內(nèi)部實(shí)現(xiàn)的一些基本原理給寫(xiě)明白了命雀。希望這篇文章能夠幫到一些人蒜哀,同時(shí)也十分歡迎就一些疑問(wèn)或者想法直接留言進(jìn)行交流!