女朋友跟我吐槽Java中ArrayList遍歷時刪除元素的各種姿勢

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"簡介"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們在項目開發過程中,經常會有需求需要刪除ArrayList中的某個元素,而使用不正確的刪除方式,就有可能拋出異常。以及在面試中,經常會遇到面試官詢問ArrayList遍歷時如何正確刪除元素。所以在本篇文章中,我們會對幾種刪除元素的方式進行測試,並對原理進行研究,希望可以幫助到大家!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ArrayList遍歷時刪除元素的幾種姿勢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先結論如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第1種方法 - 普通for循環正序刪除(結果:會漏掉元素判斷)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第2種方法 - 普通for循環倒序刪除(結果:正確刪除)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第3種方法 - for-each循環刪除(結果:拋出異常)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第4種方法 - Iterator遍歷,使用ArrayList.remove()刪除元素(結果:拋出異常)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第5種方法 - Iterator遍歷,使用Iterator的remove刪除元素(結果:正確刪除)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面讓我們來詳細探究一下原因吧!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先初始化一個數組arrayList,假設我們要刪除等於3的元素。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public static void main(String[] args) {\n ArrayList arrayList = new ArrayList();\n arrayList.add(1);\n arrayList.add(2);\n arrayList.add(3);\n arrayList.add(3);\n arrayList.add(4);\n arrayList.add(5);\n removeWayOne(arrayList);\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第1種方法 - 普通for循環正序刪除(結果:會漏掉元素判斷)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"for (int i = 0; i < arrayList.size(); i++) {\n\tif (arrayList.get(i) == 3) {//3是要刪除的元素\n\t\tarrayList.remove(i);\n\t\t//解決方案: 加一行代碼i = i - 1; 刪除元素後,下標減1\n\t}\n System.out.println(\"當前arrayList是\"+arrayList.toString());\n}\n//原ArrayList是[1, 2, 3, 3, 4, 5]\n//刪除後是[1, 2, 3, 4, 5]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 4, 5]\n當前arrayList是[1, 2, 3, 4, 5]\n當前arrayList是[1, 2, 3, 4, 5]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到少刪除了一個3,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原因在於調用remove刪除元素時,remove方法調用System.arraycopy()方法將後面的元素移動到前面的位置,也就是第二個3會移動到數組下標爲2的位置,而在下一次循環時,i+1之後,i會爲3,不會對數組下標爲2這個位置進行判斷,所以這種寫法,在刪除元素時,被刪除元素a的後一個元素b會移動a的位置,而i已經加1,會忽略對元素b的判斷,所以如果是連續的重複元素,會導致少刪除。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解決方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以在刪除元素後,執行i=i-1,使得下次循環時再次對該數組下標進行判斷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第2種方法 - 普通for循環倒序刪除(結果:正確刪除)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" for (int i = arrayList.size() -1 ; i>=0; i--) {\n if (arrayList.get(i).equals(3)) {\n arrayList.remove(i);\n }\n System.out.println(\"當前arrayList是\"+arrayList.toString());\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 4, 5]\n當前arrayList是[1, 2, 4, 5]\n當前arrayList是[1, 2, 4, 5]\n當前arrayList是[1, 2, 4, 5]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種方法可以正確刪除元素,因爲調用remove刪除元素時,remove方法調用System.arraycopy()將被刪除元素a後面的元素向前移動,而不會影響元素a之前的元素,所以倒序遍歷可以正常刪除元素。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第3種方法 - for-each循環刪除(結果:拋出異常)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static void removeWayThree(ArrayList arrayList) {\n for (Integer value : arrayList) {\n if (value.equals(3)) {//3是要刪除的元素\n arrayList.remove(value);\n }\n System.out.println(\"當前arrayList是\"+arrayList.toString());\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 4, 5]\nException in thread \"main\" java.util.ConcurrentModificationException\n\tat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)\n\tat java.util.ArrayList$Itr.next(ArrayList.java:851)\n\tat com.test.ArrayListTest1.removeWayThree(ArrayListTest1.java:50)\n\tat com.test.ArrayListTest1.main(ArrayListTest1.java:24)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"會拋出ConcurrentModificationException異常,主要在於for-each的底層實現是使用ArrayList.iterator的hasNext()方法和next()方法實現的,我們可以使用反編譯進行驗證,對包含上面的方法的類使用以下命令反編譯驗證"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"javac ArrayTest.java//生成ArrayTest.class文件\njavap -c ArrayListTest.class//對class文件反編譯"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"得到removeWayThree方法的反編譯代碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public static void removeWayThree(java.util.ArrayList);\n Code:\n 0: aload_0\n 1: invokevirtual #12 // Method java/util/ArrayList.iterator:()Ljava/util/Iterator;\n 4: astore_1\n 5: aload_1\n 6: invokeinterface #13, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 調用Iterator.hasNext()方法\n 11: ifeq 44\n 14: aload_1\n 15: invokeinterface #14, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;調用Iterator.next()方法\n 20: checkcast #9 // class java/lang/Integer\n 23: astore_2\n 24: aload_2\n 25: iconst_3\n 26: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;\n 29: invokevirtual #10 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z \n 32: ifeq 41\n 35: aload_0\n 36: aload_2\n 37: invokevirtual #15 // Method java/util/ArrayList.remove:(Ljava/lang/Object;)Z\n 40: pop\n 41: goto 5\n 44: return"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以很清楚得看到Iterator.hasNext()來判斷是否還有下一個元素,和Iterator.next()方法來獲取下一個元素。而因爲在刪除元素時,remove()方法會調用fastRemove()方法,其中會對modCount+1,代表對數組進行了修改,將修改次數+1。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public boolean remove(Object o) {\n if (o == null) {\n for (int index = 0; index < size; index++)\n if (elementData[index] == null) {\n fastRemove(index);\n return true;\n }\n } else {\n for (int index = 0; index < size; index++)\n if (o.equals(elementData[index])) {\n fastRemove(index);\n return true;\n }\n }\n \t\treturn false;\n}\n\nprivate void fastRemove(int index) {\n modCount++;\n int numMoved = size - index - 1;\n if (numMoved > 0)\n \t\t\tSystem.arraycopy(elementData, index+1, elementData, index,numMoved);\n elementData[--size] = null; // clear to let GC do its work\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而當刪除完元素後,進行下一次循環時,會調用下面源碼中Itr.next()方法獲取下一個元素,會調用checkForComodification()方法對ArrayList進行校驗,判斷在遍歷ArrayList是否已經被修改,由於之前對modCount+1,而expectedModCount還是初始化時ArrayList.Itr對象時賦的值,所以會不相等,然後拋出ConcurrentModificationException異常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"那麼有什麼辦法可以讓expectedModCount及時更新呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到下面Itr的源碼中,在Itr.remove()方法中刪除元素後會對 expectedModCount更新,所以我們在使用刪除元素時使用Itr.remove()方法來刪除元素就可以保證expectedModCount的更新了,具體看第5種方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private class Itr implements Iterator {\n int cursor; // 遊標\n int lastRet = -1; // index of last element returned; -1 if no such\n int expectedModCount = modCount;//期待的modCount值\n\n public boolean hasNext() {\n return cursor != size;\n }\n\n @SuppressWarnings(\"unchecked\")\n public E next() {\n checkForComodification();//判斷expectedModCount與當前的modCount是否一致\n int i = cursor;\n if (i >= size)\n throw new NoSuchElementException();\n Object[] elementData = ArrayList.this.elementData;\n if (i >= elementData.length)\n throw new ConcurrentModificationException();\n cursor = i + 1;\n return (E) elementData[lastRet = i];\n }\n\n public void remove() {\n if (lastRet < 0)\n throw new IllegalStateException();\n checkForComodification();\n try {\n ArrayList.this.remove(lastRet);\n cursor = lastRet;\n lastRet = -1;\n expectedModCount = modCount;//更新expectedModCount\n } catch (IndexOutOfBoundsException ex) {\n throw new ConcurrentModificationException();\n }\n }\n\n final void checkForComodification() {\n if (modCount != expectedModCount)\n throw new ConcurrentModificationException();\n }\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第4種方法 - Iterator遍歷,使用ArrayList.remove()刪除元素(結果:拋出異常)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Iterator iterator = arrayList.iterator();\nwhile (iterator.hasNext()) {\n Integer value = iterator.next();\n if (value.equals(3)) {//3是要刪除的元素\n \t\tarrayList.remove(value);\n }\n System.out.println(\"當前arrayList是\"+arrayList.toString());\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 4, 5]\nException in thread \"main\" java.util.ConcurrentModificationException\n\tat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)\n\tat java.util.ArrayList$Itr.next(ArrayList.java:851)\n\tat com.test.ArrayListTest1.removeWayFour(ArrayListTest1.java:61)\n\tat com.test.ArrayListTest1.main(ArrayListTest1.java:25)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第3種方法在編譯後的代碼,其實是跟第4種是一樣的,所以第四種寫法也會拋出ConcurrentModificationException異常。這種需要注意的是,每次調用iterator的next()方法,會導致遊標向右移動,從而達到遍歷的目的。所以在單次循環中不能多次調用next()方法,不然會導致每次循環時跳過一些元素,我在一些博客裏面看到了一些錯誤的寫法,比如這一篇"},{"type":"link","attrs":{"href":"https://juejin.im/post/5b92844a6fb9a05d290ed46c","title":""},"content":[{"type":"text","text":"《在ArrayList的循環中刪除元素,會不會出現問題?》"}]},{"type":"text","text":"文章中:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":"image-20200101124822998","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先調用iterator.next()獲取元素,與elem進行比較,如果相等,再調用list.remove(iterator.next());來移除元素,這個時候的iterator.next()其實已經不是與elem相等的元素了,而是後一個元素了,我們可以寫個demo來測試一下"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ArrayList arrayList = new ArrayList();\narrayList.add(1);\narrayList.add(2);\narrayList.add(3);\narrayList.add(4);\narrayList.add(5);\narrayList.add(6);\narrayList.add(7);\n\nInteger elem = 3;\nIterator iterator = arrayList.iterator();\nwhile (iterator.hasNext()) {\n System.out.println(arrayList);\n if(iterator.next().equals(elem)) {\n \t\tarrayList.remove(iterator.next());\n }\n} "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"[1, 2, 3, 4, 5, 6, 7]\n[1, 2, 3, 4, 5, 6, 7]\n[1, 2, 3, 4, 5, 6, 7]\n[1, 2, 3, 5, 6, 7]\nException in thread \"main\" java.util.ConcurrentModificationException\n\tat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)\n\tat java.util.ArrayList$Itr.next(ArrayList.java:851)\n\tat com.test.ArrayListTest1.main(ArrayListTest1.java:29)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到移除的元素其實不是3,而是3之後的元素,因爲調用了兩次next()方法,導致遊標多移動了。所以應該使用Integer value = iterator.next();將元素取出進行判斷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第5種方法 - Iterator遍歷,使用Iterator的remove刪除元素(結果:正確刪除)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Iterator iterator = arrayList.iterator();\nwhile (iterator.hasNext()) {\n Integer value = iterator.next();\n if (value.equals(3)) {//3是需要刪除的元素\n iterator.remove();\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 3, 4, 5]\n當前arrayList是[1, 2, 3, 4, 5]\n當前arrayList是[1, 2, 4, 5]\n當前arrayList是[1, 2, 4, 5]\n當前arrayList是[1, 2, 4, 5]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以正確刪除元素。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"跟第3種和第4種方法的區別在於是使用iterator.remove();來移除元素,而在remove()方法中會對iterator的expectedModCount變量進行更新,所以在下次循環調用iterator.next()方法時,expectedModCount與modCount相等,不會拋出異常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"HashMap遍歷時刪除元素的幾種姿勢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先結論如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第1種方法 - for-each遍歷HashMap.entrySet,使用HashMap.remove()刪除(結果:拋出異常)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第2種方法-for-each遍歷HashMap.keySet,使用HashMap.remove()刪除(結果:拋出異常)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第3種方法-使用HashMap.entrySet().iterator()遍歷刪除(結果:正確刪除)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面讓我們來詳細探究一下原因吧!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HashMap的遍歷刪除方法與ArrayList的大同小異,只是api的調用方式不同。首先初始化一個HashMap,我們要刪除key包含\"3\"字符串的鍵值對。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"HashMap hashMap = new HashMap();\nhashMap.put(\"key1\",1);\nhashMap.put(\"key2\",2);\nhashMap.put(\"key3\",3);\nhashMap.put(\"key4\",4);\nhashMap.put(\"key5\",5);\nhashMap.put(\"key6\",6);"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第1種方法 - for-each遍歷HashMap.entrySet,使用HashMap.remove()刪除(結果:拋出異常)"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"for (Map.Entry entry: hashMap.entrySet()) {\n String key = entry.getKey();\n if(key.contains(\"3\")){\n hashMap.remove(entry.getKey());\n }\n System.out.println(\"當前HashMap是\"+hashMap+\" 當前entry是\"+entry);\n\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"當前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 當前entry是key1=1\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 當前entry是key2=2\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 當前entry是key5=5\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 當前entry是key6=6\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 當前entry是key3=3\nException in thread \"main\" java.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)\n\tat java.util.HashMap$EntryIterator.next(HashMap.java:1463)\n\tat java.util.HashMap$EntryIterator.next(HashMap.java:1461)\n\tat com.test.HashMapTest.removeWayOne(HashMapTest.java:29)\n\tat com.test.HashMapTest.main(HashMapTest.java:22)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第2種方法-for-each遍歷HashMap.keySet,使用HashMap.remove()刪除(結果:拋出異常)"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Set keySet = hashMap.keySet();\nfor(String key : keySet){\n if(key.contains(\"3\")){\n keySet.remove(key);\n }\n System.out.println(\"當前HashMap是\"+hashMap+\" 當前key是\"+key);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"當前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 當前key是key1\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 當前key是key2\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 當前key是key5\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 當前key是key6\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 當前key是key3\nException in thread \"main\" java.util.ConcurrentModificationException\n\tat java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)\n\tat java.util.HashMap$KeyIterator.next(HashMap.java:1453)\n\tat com.test.HashMapTest.removeWayTwo(HashMapTest.java:40)\n\tat com.test.HashMapTest.main(HashMapTest.java:23)"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"第3種方法-使用HashMap.entrySet().iterator()遍歷刪除(結果:正確刪除)"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Iterator> iterator = hashMap.entrySet().iterator();\nwhile (iterator.hasNext()) {\n Map.Entry entry = iterator.next();\n if(entry.getKey().contains(\"3\")){\n iterator.remove();\n }\n System.out.println(\"當前HashMap是\"+hashMap+\" 當前entry是\"+entry);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"當前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 當前entry是key1=1\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 當前entry是key2=2\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 當前entry是key5=5\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 當前entry是key6=6\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 當前entry是key4=4\n當前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 當前entry是deletekey=3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第1種方法和第2種方法拋出ConcurrentModificationException異常與上面ArrayList錯誤遍歷-刪除方法的原因一致,HashIterator也有一個expectedModCount,在遍歷時獲取下一個元素時,會調用next()方法,然後對"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"expectedModCount和modCount進行判斷,不一致就拋出ConcurrentModificationException異常。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"abstract class HashIterator {\n Node next; // next entry to return\n Node current; // current entry\n int expectedModCount; // for fast-fail\n int index; // current slot\n\n HashIterator() {\n expectedModCount = modCount;\n Node[] t = table;\n current = next = null;\n index = 0;\n if (t != null && size > 0) { // advance to first entry\n do {} while (index < t.length && (next = t[index++]) == null);\n }\n }\n\n public final boolean hasNext() {\n return next != null;\n }\n\n final Node nextNode() {\n Node[] t;\n Node e = next;\n if (modCount != expectedModCount)\n throw new ConcurrentModificationException();\n if (e == null)\n throw new NoSuchElementException();\n if ((next = (current = e).next) == null && (t = table) != null) {\n do {} while (index < t.length && (next = t[index++]) == null);\n }\n return e;\n }\n\n public final void remove() {\n Node p = current;\n if (p == null)\n throw new IllegalStateException();\n if (modCount != expectedModCount)\n throw new ConcurrentModificationException();\n current = null;\n K key = p.key;\n removeNode(hash(key), key, null, false, false);\n expectedModCount = modCount;\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"PS:ConcurrentModificationException是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據ConcurrentModificationException的文檔介紹,一些對象不允許併發修改,當這些修改行爲被檢測到時,就會拋出這個異常。(例如一些集合不允許一個線程一邊遍歷時,另一個線程去修改這個集合)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一些集合(例如Collection, Vector, ArrayList,LinkedList, HashSet, Hashtable, TreeMap, AbstractList, Serialized Form)的Iterator實現中,如果提供這種併發修改異常檢測,那麼這些Iterator可以稱爲是\"fail-fast Iterator\",意思是快速失敗迭代器,就是檢測到併發修改時,直接拋出異常,而不是繼續執行,等到獲取到一些錯誤值時在拋出異常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"異常檢測主要是通過modCount和expectedModCount兩個變量來實現的,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"modCount "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"集合被修改的次數,一般是被集合(ArrayList之類的)持有,每次調用add(),remove()方法會導致modCount+1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"expectedModCount "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"期待的modCount,一般是被Iterator(ArrayList.iterator()方法返回的iterator對象)持有,一般在Iterator初始化時會賦初始值,在調用Iterator的remove()方法時會對expectedModCount進行更新。(可以看看上面的ArrayList.Itr源碼)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後在Iterator調用next()遍歷元素時,會調用checkForComodification()方法比較modCount和expectedModCount,不一致就拋出ConcurrentModificationException。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單線程操作Iterator不當時也會拋出ConcurrentModificationException異常。(上面的例子就是)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"WechatIMG4995.jpeg"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/36/3664bc238eaa6e0fe36e65ee5a3ac62e.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲ArrayList和HashMap的Iterator都是上面所說的“fail-fast Iterator”,Iterator在獲取下一個元素,刪除元素時,都會比較expectedModCount和modCount,不一致就會拋出異常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以當使用Iterator遍歷元素(for-each遍歷底層實現也是Iterator)時,需要刪除元素,一定需要使用 "},{"type":"text","marks":[{"type":"strong"}],"text":"Iterator的remove()方法"},{"type":"text","text":" 來刪除,而不是直接調用ArrayList或HashMap自身的remove()方法,否則會導致Iterator中的expectedModCount沒有及時更新,之後獲取下一個元素或者刪除元素時,expectedModCount和modCount不一致,然後拋出ConcurrentModificationException異常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"最後"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PS:本文已收錄到1.2K+ Star數開源學習指南——《大廠面試指北》,如果想要了解更多,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"請訪問"},{"type":"link","attrs":{"href":"http://notfound9.github.io/interviewGuide/","title":""},"content":[{"type":"text","text":"http://notfound9.github.io/interviewGuide/"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章