移除List中的元素,你的姿势对了吗?
之前遇到對List進行遍歷刪除的時候,出現來一個ConcurrentModificationException 異常,可能好多人都知道list遍歷不能直接進行刪除操作,但是你可能只是跟我一樣知道結果,但是不知道為什么不能刪除,或者說這個報錯是如何產生的,那么我們今天就來研究一下。
?
一、異常代碼
我們先看下這段代碼,你有沒有寫過類似的代碼
public?static?void?main(String[]?args)?{List<Integer>?list?=?new?ArrayList<>();System.out.println("開始添加元素?size:"?+?list.size());for?(int?i?=?0;?i?<?100;?i++)?{list.add(i?+?1);}System.out.println("元素添加結束?size:"?+?list.size());Iterator<Integer>?iterator?=?list.iterator();while?(iterator.hasNext())?{Integer?next?=?iterator.next();if?(next?%?5?==?0)?{list.remove(next);}}System.out.println("執行結束?size:"?+?list.size()); }「毫無疑問,執行這段代碼之后,必然報錯,我們看下報錯信息。」
我們可以通過錯誤信息可以看到,具體的錯誤是在checkForComodification 這個方法產生的。
?
二、ArrayList源碼分析
首先我們看下ArrayList的iterator這個方法,通過源碼可以發現,其實這個返回的是ArrayList內部類的一個實例對象。
public?Iterator<E>?iterator()?{return?new?Itr(); }我們看下Itr類的全部實現。
private?class?Itr?implements?Iterator<E>?{int?cursor;???????//?index?of?next?element?to?returnint?lastRet?=?-1;?//?index?of?last?element?returned;?-1?if?no?suchint?expectedModCount?=?modCount;Itr()?{}public?boolean?hasNext()?{return?cursor?!=?size;}@SuppressWarnings("unchecked")public?E?next()?{checkForComodification();int?i?=?cursor;if?(i?>=?size)throw?new?NoSuchElementException();Object[]?elementData?=?ArrayList.this.elementData;if?(i?>=?elementData.length)throw?new?ConcurrentModificationException();cursor?=?i?+?1;return?(E)?elementData[lastRet?=?i];}public?void?remove()?{if?(lastRet?<?0)throw?new?IllegalStateException();checkForComodification();try?{ArrayList.this.remove(lastRet);cursor?=?lastRet;lastRet?=?-1;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?trafficcursor?=?i;lastRet?=?i?-?1;checkForComodification();}final?void?checkForComodification()?{if?(modCount?!=?expectedModCount)throw?new?ConcurrentModificationException();} }「參數說明:」
cursor : 下一次訪問的索引;
lastRet :上一次訪問的索引;
expectedModCount :對ArrayList修改次數的期望值,初始值為modCount;
modCount :它是AbstractList的一個成員變量,表示ArrayList的修改次數,通過add和remove方法可以看出;
「幾個常用方法:」
hasNext():
public?boolean?hasNext()?{return?cursor?!=?size; }如果下一個訪問元素的下標不等于size,那么就表示還有元素可以訪問,如果下一個訪問的元素下標等于size,那么表示后面已經沒有可供訪問的元素。因為最后一個元素的下標是size()-1,所以當訪問下標等于size的時候必定沒有元素可供訪問。
next():
public?E?next()?{checkForComodification();int?i?=?cursor;if?(i?>=?size)throw?new?NoSuchElementException();Object[]?elementData?=?ArrayList.this.elementData;if?(i?>=?elementData.length)throw?new?ConcurrentModificationException();cursor?=?i?+?1;return?(E)?elementData[lastRet?=?i]; }注意下,這里面有兩個非常重要的地方,cursor初始值是0,獲取到元素之后,cursor 加1,那么它就是下次索要訪問的下標,最后一行,將i賦值給了lastRet這個其實就是上次訪問的下標。
此時,cursor變為了1,lastRet變為了0。
最后我們看下ArrayList的remove()方法做了什么?
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; }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 }「重點:」
我們先記住這里,modCount初始值是0,刪除一個元素之后,modCount自增1,接下來就是刪除元素,最后一行將引用置為null是為了方便垃圾回收器進行回收。
?
三、問題定位
到這里,其實一個完整的判斷、獲取、刪除已經走完了,此時我們回憶下各個變量的值:
cursor : 1(獲取了一次元素,默認值0自增了1);
lastRet :0(上一個訪問元素的下標值);
expectedModCount :0(初始默認值);
modCount :1(進行了一次remove操作,變成了1);
不知道你還記不記得,next()方法中有兩次檢查,如果已經忘記的話,建議你往上翻一翻,我們來看下這個判斷:
final?void?checkForComodification()?{if?(modCount?!=?expectedModCount)throw?new?ConcurrentModificationException(); }當modCount不等于expectedModCount的時候拋出異常,那么現在我們可以通過上面各變量的值發現,兩個變量的值到底是多少,并且知道它們是怎么演變過來的。那么現在我們是不是清楚了ConcurrentModificationException異常產生的愿意呢!
「就是因為,list.remove()導致modCount與expectedModCount的值不一致從而引發的問題。」
?
四、解決問題
我們現在知道引發這個問題,是因為兩個變量的值不一致所導致的,那么有沒有什么辦法可以解決這個問題呢!答案肯定是有的,通過源碼可以發現,Iterator里面也提供了remove方法。
public?void?remove()?{if?(lastRet?<?0)throw?new?IllegalStateException();checkForComodification();try?{ArrayList.this.remove(lastRet);cursor?=?lastRet;lastRet?=?-1;expectedModCount?=?modCount;}?catch?(IndexOutOfBoundsException?ex)?{throw?new?ConcurrentModificationException();} }你看它做了什么,它將modCount的值賦值給了expectedModCount,那么在調用next()進行檢查判斷的時候勢必不會出現問題。
那么以后如果需要remove的話,千萬不要使用list.remove()了,而是使用iterator.remove(),這樣其實就不會出現異常了。
public?static?void?main(String[]?args)?{List<Integer>?list?=?new?ArrayList<>();System.out.println("開始添加元素?size:"?+?list.size());for?(int?i?=?0;?i?<?100;?i++)?{list.add(i?+?1);}System.out.println("元素添加結束?size:"?+?list.size());Iterator<Integer>?iterator?=?list.iterator();while?(iterator.hasNext())?{Integer?next?=?iterator.next();if?(next?%?5?==?0)?{iterator.remove();}}System.out.println("執行結束?size:"?+?list.size()); }「建議:」
另外告訴大家,我們在進行測試的時候,如果找不到某個類的實現類,因為有時候一個類有超級多的實現類,但是你不知道它到底調用的是哪個,那么你就通過debug的方式進行查找,是很便捷的方法。
?
五、總結
其實這個問題很常見,也是很簡單,但是我們做技術的就是把握細節,通過追溯它的具體實現,發現它的問題所在,這樣你不僅僅知道這樣有問題,而且你還知道這個問題具體是如何產生的,那么今后不論對于你平時的工作還是面試都是莫大的幫助。
本期分享就到這里,謝謝各位看到此處,
記得點個贊呦!
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的移除List中的元素,你的姿势对了吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java - 朴素贝叶斯
- 下一篇: 数据事业部/数据项目/数据乐高