带你图文解析ArrayList源码
/? ?今日科技快訊? ?/
近日,在納斯達克交易中,蘋果股價下跌了近2%,此前該公司表示,新冠肺炎疫情的影響將使其無法實現本季度的銷售目標。這一消息并非完全出乎意料,因為蘋果受新冠肺炎疫情的影響已經有幾周時間,其后果是導致了一些生產延遲和中國大陸零售店關閉。
/? ?作者簡介? ?/
本篇文章轉載自李詩雨的博客,對ArrayList的源碼進行了圖文并茂的解析,希望對大家有所幫助!
作者的博客地址:
https://juejin.im/user/582c7f462e958a0069b6f068
/? ?前言? ?/
昨天玩了很久的arraycopy,今天讓我們來看看ArrayList的源碼吧。是的,又到了發揮我拙劣的畫技的時候了~
先預覽一下本篇文章的大綱:
下面我們就開始ArrayList的源碼圖解之旅吧,先從增刪改查講起,然后再講ArrayListIterator的源碼,當然這之間我們還會穿插講一下幾個大坑及注意事項~
/? ?增? ?/
ArrayList添加元素的操作,涉及到2個方法 add(E object)?和 add(int index, E object)?。
add(E object) 直接在尾部添加一個元素
add(E object)這個方法,是直接添加一個元素,是在尾部進行插入。
如果原數組的大小不夠會先進行擴容。最終將數據添加在尾部,同時大小加1。源碼如下:
add(int index, E object) 在指定位置添加一個元素
add(int index, E object)這個函數是指在 index的位置 ,插入一個新元素。
我們先看插入操作的核心步驟:
從要插入的位置開始的所有數據,都要往先后挪一位;然后再把要插入的數據放進去。畫了個圖,方便大家理解:
了解了插入的核心步驟之后,我們就來看看 add(int index, E object)的源碼中,插入一個元素到指定位置具體是怎么實現的吧。
一、要先檢測要插入的位置index是否合法。
二、判斷數組是否夠用:
如果數組夠用,即 s<a.length時,直接調用arraycopy(),將從index開始的所有數據都往后挪一位。(關于arraycopy的使用我上一篇已經詳細講過了,所以這里我們應該知道他是從后往前依次往后挪一位過去的)。
如果數組滿了,即s>=a.length時,這里就要:
先將原數組進行擴容,生成新的數組;
將原數組中index之前的數據復制到新數組對應的位置中去。
將原數組中index之后的數據往后挪一位的移動到新的數組中去。
將新數組賦給array。
三、將要添加的元素放在index的位置,同時有效數據個數size+1。
源代碼見下圖:
補充:擴容規則
我們都知道,數組的大小是不可變的,而ArrayList的大小是可變的。而ArrayList底層也是用到了數組,那ArrayList是如何做到大小可以動態變化的呢?
答案就是通過擴容的方式。
也就是Object[] newArray = new Object[newCapacity(s)];這句代碼。
現在我們來具體看看newCapacity(s)的實現:
這里我們做一下解釋:
當目前的容量currentCapacity<6 時,increment=12;否則的話 increment等于currentCapacity的2倍。最終返回的大小是currentCapacity+increment。
也就是說,擴容之后要么是在原有的基礎上 +12,要么就是擴大為原來的三倍。
/? ?刪? ?/
ArrayList的刪除操作,我們也來看兩個remove(Object object)和remove(int index)。
remove(int index) 刪除指定位置的元素
同樣,我們先來看看刪除的核心操作:
對應的代碼就是System.arraycopy(a, index + 1, a, index, --s - index);這句代碼,
即 :如果要刪除【index】位置的元素,那就要把【index】之后的所有元素都往前挪一位,覆蓋掉index原本的位置。
同樣來畫個圖來幫助大家理解:
了解了核心的操作之后,我們就來看看 remove(int index)?的源代碼吧:
remove(Object object) 刪除某個已知元素
上面我們已經知道了刪除指定位置元素的操作,那如果要刪除某個已知元素的話,我們是不是也應該先找到它對應的位置,然后再進行刪除呢。
那問題來了,怎么找到元素對應的位置呢?
對,通過循環遍歷,并進行比較 找到對應的index。
▲有個坑!
▲【注意】:
調用remove方法, 會, 且只會 刪除第一個與傳入對象通過equals方法判斷相等的元素。
如果傳入null,則刪除掉第一個null元素。所以,如果自定義類想要使用remove方法從列表刪除某個指定值對象,還需要實現該類型自己的equals方法才行!
▲還有個坑!
ArrayList是可以順序刪除節點的,但是!如果使用普通for循環,必須是從后往前刪。不能從前往后刪。
我們先來看一下【錯誤示范】:
ArrayList?list=new?ArrayList(); list.add("a"); list.add("b"); list.add("c");System.out.println("刪除前:"+list.toString());//順序刪除節點錯誤示范:從前往后刪----會刪不干凈 for?(int?i=0;i<list.size();i++){list.remove(i); } System.out.println("刪除后:"+list.toString());【錯誤結果展示】:
【出錯原因分析】:
要順序刪除ArrayList的全部節點,如果我們從前往后的順序刪除,先刪除【0】位置的數據,但是由于刪除的時候是從后往前挪一位進行刪除的,所以【0】的位置又會被下一個位置的數據覆蓋上,實際上【0】還是有數據的。再畫一張圖來方便大家理解:
【正確的做法】:
要想順序刪除ArrayList的所有節點,且采用普通的for循環,那只能從后往前刪,這樣就不會出問題。
/? ?改、查? ?/
ArrayList修改數據很簡單,調用的是set(int index, E object)方法,直接修改對應位置的數據即可。
ArrayList獲取數據就更簡單了,由于是順序表有下標,直接取出對應下標數據就可以了。
/? ?ArrayList的三種遍歷方式? ?/
ArrayList的遍歷我們有三種方式:for循環,增強for循環?和?迭代器三種方式。
(當然,增強for循環其實還是用迭代器實現的,這一點我們可以通過反編譯來進行驗證。)
ArrayList?arrayList?=?new?ArrayList(); arrayList.add("情人節"); arrayList.add("快樂"); arrayList.add("我"); arrayList.add("對"); arrayList.add("自己說~");System.out.println("for循環的方式遍歷:"); for?(int?i?=?0;?i?<?arrayList.size();?i++)?{System.out.print(arrayList.get(i)); }System.out.println(); System.out.println("---------------------------------"); System.out.println("增強for循環的方式遍歷:"); 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()); }我們來看一下打印結果:
/? ?迭代器ArrayListIterator源碼解讀? ?/
好的,上面我們知道了ArrayList可以使用迭代器進行遍歷。
那為什么它可以使用迭代器這種方式呢?
這個我們又要去看源碼了,跟進arrayList.iterator()的 iterator()方法,我們會發現ArrayList有一個內部類ArrayListIterator。而 ArrayListIterator 實現了 Iterator 接口,所以才可以使用迭代器這種方式進行迭代。
現在我們就來具體看看ArrayListIterator的相關源代碼和注意事項吧。
首先,我們可以看到ArrayListIterator實現了Iterator這個接口
提一下什么是 Iterator (迭代器)?
我們都知道在Java中,有很多的數據容器,這些的操作又有很多的共性。而迭代器就是給各種容器提供了公共的操作接口。這樣就使得對容器的操作有了規范性。
在Iterator接口中定義了三個方法:
hasNext(): 如果仍有元素可以迭代,就返回true.
next(): 返回迭代的下一個元素。
remove(): 從集合中移除返回的最后一個對象。(可選操作)
源碼如下:
ArrayListIterator中的坑
ArrayListIterator 的源碼其實并不難理解,就是實現了 Iterator 中的三個方法。但是這里有一個▲坑▲大家需要注意,那就是:
每當我們使用迭代器遍歷元素時,如果使用迭代器以外的方法修改了元素內容(如刪除元素),那就會拋出ConcurrentModificationException的異常。
讓我先看一下現象,然后再從源碼角度找原因。
錯誤代碼示例:
????????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);報錯顯示:
好的,現象我們已經看到了,那現在我們就來看看錯誤的原因吧。
我們要先來了解一下這幾個變量的含義:
然后我們來看一下何種情況下會報錯:
先分析一下報錯原因:
在我們使用 ArrayLis 的 iterator() 方法獲取到迭代器進行遍歷時,會把 ArrayList 當前狀態下的 modCount 賦值給 ArrayListIterator類的 expectedModCount 屬性。
如果我們在迭代過程中,使用了 ArrayList 的 remove()方法,這時 modCount 就會加 1 ,但是迭代器中的expectedModCount 并沒有變化,當我們再使用迭代器的next()方法時,它就會報ConcurrentModificationException的錯。
最后我們再來比較一下 ArrayListIterator中的 remove()方法和ArrayList自己的remove()方法的不同之處,驗證一下錯誤發生的原因:
所以我們得到的啟示是:
每當我們使用迭代器遍歷元素時,要使用迭代器自己的刪除方法,而不能使用迭代器以外的方法修改了元素內容,否則會造成expectedModCount和modCount的值不一致,從而拋出ConcurrentModificationException的異常。
此外,我們還要注意一下,增強for循環其實也是使用的迭代器,所以也要注意同樣的問題。
推薦閱讀:
Kotlin中關于泛型型變的那些事
Dart語言快速入門
這份AS快捷鍵大全,讓你的開發效率快10倍
歡迎關注我的公眾號
學習技術或投稿
長按上圖,識別圖中二維碼即可關注
總結
以上是生活随笔為你收集整理的带你图文解析ArrayList源码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 时间,星期
- 下一篇: maven 命令指定配置文件打包spri