文章目錄
- 1. 遍歷
- 2. 遍歷過程中刪除元素
- 2.1 for 簡單循環正向遍歷方式
- 2.2 for 簡單循環反向遍歷方式
- 2.3 foreach 方式遍歷刪除
- 2.4 Iterator的remove()方法
- 2.5 <font color = green> removeIf() (推薦)<green>
- 2.6 Strem 方式
作為一名后端開發,不管采用什么語言 使用 List 集合的頻率都非常高。
對 List 集合的遍歷和遍歷中操作數據也是家常便飯。
我從我使用的過程中對于此問題的思考與實踐形成記錄,與大家交流。有不對的地方,懇請大家指正。
1. 遍歷
List 集合的遍歷有很多種方式,每一種遍歷方式只有性能上的差異,不會有異常和暗坑。這里不再贅述。
2. 遍歷過程中刪除元素
因為 List 集合有多種遍歷方式,也就意味著存在多種遍歷中刪除的方式:
在做各種方式比對前,我們先加入一些初始數據。
List<String> list = new ArrayList<>();list.add("snow");list.add("nier");list.add("sar");list.add("juaya");list.add("rock");list.add("snow");list.add("nier");
2.1 for 簡單循環正向遍歷方式
for (int i = 0; i < list.size(); i++) {String name = list.get(i);if(name.equals("snow")){list.remove(i);}if(name.equals("nier")){list.remove(i);}
}System.out.println(list);
上述代碼打印結果:
[nier, sar, juaya, rock, nier]
很明顯 這不是正確的結果。
這是 因為:在遍歷過程中,刪除或者增加元素后,集合長度會因為實時改動而改動,也就是說集合在遍歷過程中忘了初心了。
舉個栗子:現在對長度為 5 的集合進行遍歷,在遍歷到 第三個元素的時候刪除了ta,那么此時集合長度成了4,那么此時集合就不會遍歷到之前下標為 5 的元素了。
知道了原因后,我們可以對癥下藥,達到我們的預期。修改代碼如下:
for (int i = 0; i < list.size(); i++) {String name = list.get(i);if(name.equals("snow")){list.remove(i);// 加補償機制i--;}if(name.equals("nier")){list.remove(i);// 加補償機制i--;}
}System.out.println(list);
上述代碼執行結果:
[sar, juaya, rock]
如此 加入了補償機制后雖然達到了預期結果。但是很明顯代碼不夠優雅。So 這種方式不推薦。
2.2 for 簡單循環反向遍歷方式
反向遍歷和正向遍歷類似,只不過是反方向的鐘而已。
for (int i = list.size() - 1; i >= 0; i--) {String name = list.get(i);if(name.equals("snow")){list.remove(i);}if(name.equals("nier")){list.remove(i);}
}System.out.println(list);
上述代碼執行結果:
[sar, juaya, rock]
咦~可以哎。結果正確。那這種方式是不是就絕對可以了呢。 NO!
逆序遍歷雖然在這個場景下達到了預期的正確結果,但是因為 這種方式和正序在原理上是相同的。所以在遍歷過程中操作元素也會有其劣根性。比如在逆序遍歷過程中加入元素,依然會對整個遍歷產生影響。 所以這也不是推薦的方式。
2.3 foreach 方式遍歷刪除
單從遍歷來看 這種方式比上述兩種方式優雅了不少。但是遍歷過程中操作元素能達到預期結果嗎?
for (String s : list) {if(s.equals("rock")){list.remove("rock");}
}
執行結果…
What???
可以看出:調用next()方法獲取下一個元素時,第一行代碼就是調用了checkForComodification()
;
而該方法的核心邏輯就是比較 modCount 和 expectedModCount 這2個變量的值。
在上面的例子中,剛開始 modCount 和 expectedModCount 的值都為 n,所以第1次獲取元素 “rock” 是沒問題的,但是當執行完下面這行代碼時:
list.remove("rock");
modCount 的值就被修改了。
So: 使用 foreach 遍歷過程中刪除元素是不可行的。
2.4 Iterator的remove()方法
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){String next = iterator.next();if("snow".equals(next)){iterator.remove();}
}
執行結果:
[nier, sar, juaya, rock, nier]
很 nice !
那這種方式怎么就可以刪除了呢???
因為:
這種方式每次刪除一個元素,都會將modCount 的值重新賦值給 expectedModCount,這樣2個變量就相等了,不會觸發 java.util.ConcurrentModificationException
異常。
雖然這種方式符合了業務要求,但是還是不推薦這種方式,因為他的寫法依然不夠優雅。
2.5 removeIf() (推薦)
從JDK1.8開始,可以使用 removeIf() 方法來代替 Iterator的remove()方法實現一邊遍歷一邊刪除。
list.removeIf(s -> s.equals("snow"));System.out.println(list);
打印結果:
[nier, sar, juaya, rock, nier]
其源碼如下:
2.6 Strem 方式
list = list.stream().filter( name -> !("snow".equals(name) ) ).collect(Collectors.toList());System.out.println(list);
filter 過濾會保留滿足條件的。
結果如下:
[nier, sar, juaya, rock, nier]