當前位置: 妍妍網 > 碼農

面試官:你這樣用集合,確定不會有問題?

2024-05-05碼農

引言:不知道 fail-fast 機制,使用集合會出現各種各樣的問題,其實大家在刷力扣的時候就可能會出現這樣的場景,需要一邊遍歷集合,一邊去添加或者修改集合中的元素,點選執行程式碼,可能報 ConcurrentModificationException 異常,這就是因為錯誤使用了集合,由於 fail-fast 錯誤檢測機制,會丟擲異常,這個問題也是面試中經常會問到的問題,本文將會從以下幾個方面去講,什麽是 fail-fast 機制?什麽時候會出現這種情況?為什麽會出現這種情況?(源分碼析)怎麽解決集合的錯誤使用問題?

題目

面試官:fail-fast 機制了解嗎?你這樣使用集合不會有問題嗎?。。

推薦解析

什麽是 fail-fast 機制?

百度百科中只說明 fail-fast 機制是 Java 語言所特有的,其實是錯誤的,有小夥伴感興趣可以修改下概念。fail-fast 是通用的系統設計思想,在 Python 2 中就有 fail-fast 機制,但可以呼叫 System 方法關閉安全校驗,Python 3 直接不可關閉,推薦用異常進行捕獲,C# 中也支持 fail-fast 機制,會丟擲 InvalidOperationException 異常。

什麽時候會出現 fail-fast 機制?

建議去測試的時候,單獨測 1 個數據、2 個數據、3個數據,其實會有特例,數據量不同的時候,有些情況可能不會產生 fail-fast 機制,建議可以自己去檢測一下。

程式碼舉例如下

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
for (String list : lists) {
if (list.equals("Xiao Bai Tiao2")) {
lists.remove(list);
}
}
System.out.println(lists);

由於使用了增加 for 迴圈,但又不使用叠代器的 remove 去配合 hasNext(),此時的 remove 方法呼叫源碼如下。

源碼註釋轉譯

從此列表中刪除第一個出現的指定元素(如果該元素存在)。如果列表中不包含該元素,則該元素不變。更正式地說,刪除具有最低索引i的元素,使(o==null ?Get (i)==null: o.equals(Get (i)))(如果存在這樣的元素)。如果此列表包含指定的元素(或者等價地,如果此列表因呼叫而更改),則返回true。如果列表中包含指定的元素,則返回:true。

關鍵方法是什麽?

fastRemove 非常明顯,集合方法 只會修改 modCount 數量。

privatevoidfastRemove(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
}

點選 ConcurrentModificationException 的發生行,其實是 Iterator 叠代器的 next(),隨便找個實作 Iterator 介面的實作類即可,ArrayList,Vector,都可以,源碼搜這個方法。

finalvoidcheckForComodification(){
if (modCount != expectedModCount)
thrownew ConcurrentModificationException();
}

如果修改的數量不等於預期修改的數量(呼叫 Iterator 的 remove 才會修改 expectedModCount)。

簡單來說,普通 remove 直接呼叫了 arraylist 底層的 fastRemove 只修改了 modCount,導致叠代器的 expecteModCount 和 modCount 不一致,因此叠代器的 fail-fast 機制被觸發,會丟擲異常,不理解的小夥伴直接搜我圖中的源碼,可以打斷點進行 debug。實踐出真知.jpg。

怎麽解決 fail-fast 機制?

1)使用普通的 for 迴圈不會觸發叠代器的 fail-fast 機制,but,list 元素會減少,下表會改變,很明顯會出現漏刪的情況。

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
for(int i = 0;i<lists.length;i++){
if (lists.get(i).equals("Xiao Bai Tiao1")) {
lists.remove(list);
}
}
System.out.println(lists);

2)使用叠代器的 Remove 方法

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
Iterator iterator = lists.iterator();
while(iterator.hasNext()){
if (iterator.next().equals("Xiao Bai Tiao1")) {
iterator.remove();
}
}
System.out.println(lists);

3)使用 fail-safe 集合安全的類

ConcurrentLinkedDeque<String> lists = new ConcurrentLinkedDeque<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
for (String list : lists) {
if (list.equals("Xiao Bai Tiao1")) {
lists.remove();
}
}
System.out.println(lists);

4)Java 8 的 Stream 流

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
lists = lists.stream().filter(list -> !list.equals("Xiao Bai Tiao1")).collect(Collectors.toList());
System.out.println(lists);

5)使用增強 for 迴圈,但是刪除後立刻 break 結束。

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
for (String list : lists) {
if (list.equals("Xiao Bai Tiao2")) {
lists.remove(list);
break;
}
}
System.out.println(lists);

總結

fail-fast 就是多種語言在集合叠代遍歷的同時去修改集合元素,因此會觸發 fail-fast 機制,會丟擲某些異常,但是依然可以有方法去進行解決,使用集合時要特別註意,閱讀源碼後即可了解底層呼叫原理,15 分鐘基本就能完全理解了,現在囫圇吞棗,面試依然...建議大家還是沈下心來學習

其他補充

魚聰明 AI 的回答:

魚聰明 AI 地址:https://www.yucongming.com/

"Fail-fast"機制是一種軟體設計原則,其主要思想是在軟體出現問題時,盡早檢測並立即停止程式的執行,以防止錯誤進一步擴大,從而提高系統的穩定性和可靠性。

出現fail-fast機制的情況通常包括以下幾種:

  1. 並行修改:在多執行緒或並行環境下,如果多個執行緒同時對共享數據進行修改而沒有合適的同步措施,就可能出現並行修改的情況。為了防止數據不一致或其他潛在的問題,系統可能會選擇使用fail-fast機制來及時檢測並報告這些並行修改。

  2. 數據校驗失敗:當輸入的數據不符合預期的格式或約束條件時,系統可能會選擇使用fail-fast機制來立即報告數據校驗失敗的情況,而不是繼續執行可能會導致更嚴重錯誤的操作。

  3. 不合法狀態:當系統處於不合法或無法處理的狀態時,為了防止進一步的錯誤發生,系統可能會選擇使用fail-fast機制來立即停止執行,避免更嚴重的後果。

解決fail-fast機制的方法通常包括以下幾點:

  1. 例外處理:在出現錯誤時,使用異常機制來及時捕獲和處理異常,以防止程式終止執行。透過合理的例外處理策略,可以使程式在出現問題時能夠進行適當的處理而不至於崩潰。

  2. 錯誤檢測與修復:在編寫程式碼時,盡量避免可能導致fail-fast機制觸發的情況,例如避免在叠代集合時進行並行修改,或者在操作之前進行合適的數據校驗等。

  3. 合理的設計和測試:在軟體設計階段,考慮到可能出現的異常情況,並采取相應的措施來處理這些異常情況。同時,在開發過程中進行充分的測試,以確保程式在各種情況下都能夠正確地執行並且不會觸發fail-fast機制。

CSDN 某同學的回答

總結:之所以會丟擲ConcurrentModificationException異常,是因為foreach底層是使用iterator來遍歷,但是迴圈中元素的添加或者刪除卻是呼叫集合本身的方法,導致iterator在遍歷過程中,發現有元素在自己不知不覺的情況下添加/刪除了,就會丟擲異常,告知使用者可能會並行修改問題!

歡迎交流

在閱讀完本文後,你應該了解了 fail-fast 機制的概念、出現場景、怎麽解決 fail-fast 機制?在文末還有三個問題來檢驗你學習的情況,歡迎小夥伴在評論區積極留言!

1)fail-fast機制如何幫助提高軟體系統的穩定性和可靠性?

2)在Java、Python和C#等程式語言中,有哪些常見的情況會觸發fail-fast機制?

3)在使用fail-fast機制時,如何在程式碼中正確處理異常,以避免程式意外終止?

關註下方公眾號卡片,持續收獲重點面試題和最新的大廠面經!

往期推薦