不詩意的女程序媛不是好廚師~
轉(zhuǎn)載請注明出處诡蜓,F(xiàn)rom李詩雨---[https://blog.csdn.net/cjm2484836553/article/details/104329665]
昨天玩了很久的arraycopy捺球,今天讓我們來看看ArrayList的源碼吧粟耻。
是的,又到了發(fā)揮我拙劣的畫技的時候了~
先預(yù)覽一下本篇文章的大綱:
下面我們就開始ArrayList的源碼圖解之旅吧锨咙,先從增刪改查講起语卤,然后再講ArrayListIterator的源碼,當(dāng)然這之間我們還會穿插講一下幾個大坑及注意事項~
1.增
ArrayList添加元素的操作酪刀,涉及到2個方法 add(E object)
和 add(int index, E object)
粹舵。
1.1 add(E object) 直接在尾部添加一個元素
add(E object)
這個方法,是直接添加一個元素骂倘,是在尾部進行插入眼滤。
如果原數(shù)組的大小不夠會先進行擴容。
最終將數(shù)據(jù)添加在尾部历涝,同時大小加1.
源碼如下:
1.2 add(int index, E object) 在指定位置添加一個元素
add(int index, E object)
這個函數(shù)是指在 index的位置 诅需,插入一個新元素漾唉。
-
我們先看插入操作的核心步驟:
從要插入的位置開始的所有數(shù)據(jù),都要往先后挪一位堰塌;然后再把要插入的數(shù)據(jù)放進去赵刑。
畫了個圖 方便大家理解:
-
了解了插入的核心步驟之后,我們就來看看
add(int index, E object)
的源碼中场刑,插入一個元素到指定位置具體是怎么實現(xiàn)的吧般此。一、要先檢測要插入的位置index是否合法牵现。
二恤煞、判斷數(shù)組是否夠用:
? a. 如果數(shù)組夠用,即
s<a.length
時施籍,直接調(diào)用arraycopy()
居扒,將從index開始的所有數(shù)據(jù)都往后挪一位。(關(guān)于arraycopy的使用我上一篇已經(jīng)詳細講過了丑慎,所以這里我們應(yīng)該知道他是從后往前依次往后挪一位過去的)喜喂。? b. 如果數(shù)組滿了,即
s>=a.length
時,這里就要? (1)先將原數(shù)組進行擴容,生成新的數(shù)組身隐;
? (2)將原數(shù)組中index之前的數(shù)據(jù)復(fù)制到新數(shù)組對應(yīng)的位置中去话浇。
? (3)將原數(shù)組中index之后的數(shù)據(jù)往后挪一位的移動到新的數(shù)組中去夷野。
? (4)將新數(shù)組賦給array。
三、將要添加的元素放在index的位置,同時有效數(shù)據(jù)個數(shù)size+1影斑。
源代碼見下圖:
補充:擴容規(guī)則
我們都知道,數(shù)組的大小是不可變的机打,而ArrayList的大小是可變的矫户。而ArrayList底層也是用到了數(shù)組,那ArrayList是如何做到大小可以動態(tài)變化的呢残邀?
答案就是通過擴容的方式皆辽。
也就是Object[] newArray = new Object[newCapacity(s)];
這句代碼。
現(xiàn)在我們來具體看看newCapacity(s)
的實現(xiàn):
這里我們做一下解釋:
當(dāng)目前的容量currentCapacity
<6 時芥挣,increment=12;
否則的話 increment等于currentCapacity的一半驱闷。
最終返回的大小是 currentCapacity+increment
。
也就是說空免,擴容之后要么是在原有的基礎(chǔ)上 +12空另,要么就是擴大為原來的1.5倍。
2.刪
ArrayList的刪除操作鼓蜒,我們也來看兩個 remove(Object object)
和 remove(int index)
痹换。
2.1 remove(int index) 刪除指定位置的元素
-
同樣,我們先來看看刪除的核心操作:
對應(yīng)的代碼就是
System.arraycopy(a, index + 1, a, index, --s - index);
這句代碼都弹,即 :如果要刪除【index】位置的元素娇豫,那就要把【index】之后的所有元素都往前挪一位,覆蓋掉index原本的位置.
同樣來畫個圖來幫助大家理解:
-
了解了核心的操作之后畅厢,我們就來看看
remove(int index)
的源代碼吧:
2.2 remove(Object object) 刪除某個已知元素
remove(Object object)
刪除某個已知元素冯痢。
上面我們已經(jīng)知道了刪除指定位置元素的操作,那如果要刪除某個已知元素的話框杜,我們是不是也應(yīng)該先找到它對應(yīng)的位置浦楣,然后再進行刪除呢。
-
那問題來了咪辱,怎么找到元素對應(yīng)的位置呢振劳?
對,通過循環(huán)遍歷油狂,并進行比較 找到對應(yīng)的index.
▲有個坑历恐!
▲【注意】:
調(diào)用remove方法, 會专筷, 且只會 刪除第一個與傳入對象通過equals方法判斷相等的元素弱贼。
如果傳入null, 則刪除掉第一個null元素磷蛹。
所以吮旅, 如果自定義類想要使用remove方法從列表刪除某個指定值對象, 還需要實現(xiàn)該類型自己的equals方法才行味咳!
▲還有個坑庇勃!
ArrayList是可以順序刪除節(jié)點的,但是槽驶!如果使用普通for循環(huán)匪凉,必須是從后往前刪。不能從前往后刪捺檬。
我們先來看一下【錯誤示范】:
ArrayList list=new ArrayList();
list.add("a");
list.add("b");
list.add("c");
System.out.println("刪除前:"+list.toString());
//順序刪除節(jié)點錯誤示范:從前往后刪----會刪不干凈
for (int i=0;i<list.size();i++){
list.remove(i);
}
System.out.println("刪除后:"+list.toString());
【錯誤結(jié)果展示】:
【出錯原因分析】:
要順序刪除ArrayList的全部節(jié)點再层,如果我們從前往后的順序刪除,先刪除【0】位置的數(shù)據(jù)堡纬,但是由于刪除的時候是從后往前挪一位進行刪除的聂受,所以【0】的位置又會被下一個位置的數(shù)據(jù)覆蓋上,實際上【0】還是有數(shù)據(jù)的烤镐。再畫一張圖來方便大家理解:
【正確的做法】:
要想順序刪除ArrayList的所有節(jié)點蛋济,且采用普通的for循環(huán),那只能從后往前刪炮叶,這樣就不會出問題碗旅。
3.改渡处、查
ArrayList修改數(shù)據(jù)很簡單,調(diào)用的是set(int index, E object)
方法祟辟,直接修改對應(yīng)位置的數(shù)據(jù)即可医瘫。
ArrayList獲取數(shù)據(jù)就跟簡單了,由于是順序表有下標(biāo)旧困,直接取出對應(yīng)下標(biāo)數(shù)據(jù)就可以了醇份。
4.ArrayList的3種遍歷方式
ArrayList
的遍歷我們有三種方式:for循環(huán),增強for循環(huán) 和 迭代器三種方式吼具。
(當(dāng)然僚纷,增強for循環(huán)其實還是用迭代器實現(xiàn)的,這一點我們可以通過反編譯來進行驗證拗盒。)
ArrayList arrayList = new ArrayList();
arrayList.add("情人節(jié)");
arrayList.add("快樂");
arrayList.add("我");
arrayList.add("對");
arrayList.add("自己說~");
System.out.println("for循環(huán)的方式遍歷:");
for (int i = 0; i < arrayList.size(); i++) {
System.out.print(arrayList.get(i));
}
System.out.println();
System.out.println("---------------------------------");
System.out.println("增強for循環(huán)的方式遍歷:");
for (Object s : arrayList) {
System.out.print((String) s);
}
System.out.println();
System.out.println("---------------------------------");
System.out.println("迭代器的方式遍歷:");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next());
}
我們來看一下打印結(jié)果:
5.迭代器 ArrayListIterator源碼解讀
好的怖竭,上面我們知道了ArrayList
可以使用迭代器進行遍歷。
-
那為什么它可以使用迭代器這種方式呢陡蝇?
這個我們又要去看源碼了侵状,跟進
arrayList.iterator()
的 iterator()方法,我們會發(fā)現(xiàn)ArrayList有一個內(nèi)部類ArrayListIterator
毅整。而 ArrayListIterator 實現(xiàn)了 Iterator 接口趣兄,所以才可以使用迭代器這種方式進行迭代。
現(xiàn)在我們就來具體看看ArrayListIterator
的相關(guān)源代碼和注意事項吧悼嫉。
首先艇潭,我們可以看到ArrayListIterator
實現(xiàn)了Iterator
這個接口。
5.1 提一下什么是 Iterator
(迭代器)戏蔑?
我們都知道在Java中蹋凝,有很多的數(shù)據(jù)容器,這些的操作又有很多的共性总棵。而迭代器就是給各種容器提供了公共的操作接口鳍寂。這樣就使得對容器的操作有了規(guī)范性。
在Iterator接口中定義了三個方法:
hasNext(): 如果仍有元素可以迭代情龄,就返回true.
next(): 返回迭代的下一個元素迄汛。
remove(): 從集合中移除返回的最后一個對象。(可選操作)
源碼如下:
5.2 ArrayListIterator中的坑▲▲▲
ArrayListIterator 的源碼其實并不難理解骤视,就是實現(xiàn)了 Iterator 中的三個方法鞍爱。
但是這里有一個▲坑▲大家需要注意,那就是:
每當(dāng)我們使用迭代器遍歷元素時专酗,如果使用迭代器以外的方法修改了元素內(nèi)容(如刪除元素)睹逃,那就會拋出ConcurrentModificationException
的異常。
讓我先看一下現(xiàn)象祷肯,然后再從源碼角度找原因沉填。
錯誤代碼示例:
ArrayList arrayList = new ArrayList();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
System.out.println("移除前:" + arrayList);
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
if ("c".equals(iterator.next())) {
arrayList.remove("c");
}
}
System.out.println("移除后:" + arrayList);
//注意增強for使用的也是迭代器
//所以一下這種操作也會報ConcurrentModificationException
//for (Object o : arrayList) {
// arrayList.remove(o);
//}
//System.out.println("移除后2:" + arrayList);
報錯顯示:
好的疗隶,現(xiàn)象我們已經(jīng)看到了,那現(xiàn)在我們就來看看錯誤的原因吧翼闹。
我們要先來了解一下這幾個變量的含義:
然后我們來看一下何種情況下會報錯:
先分析一下報錯原因:
在我們使用 ArrayLis 的 iterator()
方法獲取到迭代器進行遍歷時斑鼻,會把 ArrayList 當(dāng)前狀態(tài)下的 modCount
賦值給 ArrayListIterator
類的 expectedModCount
屬性。
如果我們在迭代過程中橄碾,使用了 ArrayList 的 remove()
方法卵沉,這時 modCount
就會加 1 颠锉,但是迭代器中的expectedModCount
并沒有變化法牲,當(dāng)我們再使用迭代器的next()
方法時,它就會報ConcurrentModificationException
的錯琼掠。
最后我們再來比較一下 ArrayListIterator
中的 remove()
方法和ArrayList
自己的remove()
方法的不同之處拒垃,驗證一下錯誤發(fā)生的原因:
?所以我們得到的啟示是:
每當(dāng)我們使用迭代器遍歷元素時,要使用迭代器自己的刪除方法瓷蛙,而不能使用迭代器以外的方法修改了元素內(nèi)容悼瓮,否則會造成expectedModCount和modCount的值不一致,從而拋出ConcurrentModificationException
的異常艰猬。
此外横堡,我們還要注意一下,增強for循環(huán)其實也是使用的迭代器冠桃,所以也要注意同樣的問題命贴。
積累點滴,做好自己~