天天看點

Java 集合(五)| 集合面試彙總一、HashMap面試彙總二、List面試彙總三、補充

文章目錄

  • 一、HashMap面試彙總
      • 1.談一下HashMap的資料結構
      • 2.hash沖突是什麼,該如何避免
      • 3.HashMap中如何設計的hash算法
      • 4.hash()擾動函數為什麼将hashcode右移16位?為什麼要采用異或運算
      • 5.為什麼JDK1.8中的HashMap要使用紅黑樹這個資料結構來存儲資料
      • 6.為什麼不直接使用紅黑樹,而是在達到一定的門檻值後才轉化
      • 7.為什麼連結清單轉化為紅黑樹的門檻值為8,紅黑樹轉化為連結清單的門檻值為6
      • 8.HashMap中哈希表的長度為什麼要設計成2的幂次方
      • 9.HashMap中的key和value可以為null嗎
      • 10.除了key和value能為null,HashMap還有什麼特點
      • 11.為什麼String、Integer這樣的包裝類适合作HashMap的鍵
      • 12.為什麼重寫了equasl方法還要重寫hashCode方法
      • 13.說一說HashMap、HashTable、TreeMap、LinkedHashMap的關系
      • 14.與JDK1.7相比,JDK1.8對HashMap作了哪些優化
      • 15.HashMap的擴容機制是怎樣的?JDK1.7和JDK1.8有什麼不同
      • 16.HashMap和HashTable有什麼差別
  • 二、List面試彙總
      • 1.ArrayList和LinkedList的差別和适用場景
      • 2.ArrayList自動擴容流程是怎樣的
      • 3.ArrayList如何删除元素的
      • 4.ArrayList的預設初始化容量是多少?如何進行的初始化
      • 5.ArrayList是線程安全的嗎
  • 三、補充
      • 1.List 和 Map、Set 的差別
      • 2.并發集合和普通集合的差別
      • 3.關于fast-fail機制

一、HashMap面試彙總

1.談一下HashMap的資料結構

  • 在JDK1.8中,HashMap是由數組+連結清單+紅黑樹構成
  • 當一個值中要存儲到HashMap中的時候會根據Key的值計算出他的hash值 ,通過hash值确認存放到數組中的位置,如果發生了hash沖突就以連結清單的形式存儲,當連結清單過長的時候就會将連結清單轉化為紅黑樹
  • HashMap中的哈希桶數組(位桶數組)中存放的是Node對象,它是一個靜态内部類,用來表示key-value鍵值對

2.hash沖突是什麼,該如何避免

  • 不同的key值根據其關鍵碼通過哈希函數的運算得到了同一個hash值(散列值),即定位到了散清單中的同一個位置,造成了hash沖突

    如何避免hash沖突有兩種思路:

  • 預防:比如采用好的hash算法,設計良好的擴容機制
  • 解決:采用鍊位址法、開放位址法等來解決已經形成的hash沖突

3.HashMap中如何設計的hash算法

  • HashMap中用的是除留餘數法當作hash函數,即(哈希桶數組長度-1)&hash,其中hash指的是key經過hash()擾動函數得到的值
  • 擾動函數:将key的hashcode通過無符号右移和異或運算得到一個值,這個值比直接使用hashcode當作hash函數的輸入要好,因為它極大的避免了hash沖突
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

           

4.hash()擾動函數為什麼将hashcode右移16位?為什麼要采用異或運算

  • 16位正好是32位hashcode的一半,自己的高16位和低16位異或,可以使hashcode的所有位都參與運算,使最終運算結果的低16位加大了随機性,而原來的高16位和移位所補的0相異或,最終的結果還是取決于高16位,是以,不會對hashcode的高16位産生影響
  • 至于為什麼采用異或(^)運算,在各種0和1的運算中得到1或者0的機率都是1/2,而&(與)運算得到0的機率較大為75%,| (或)運算得到1的機率較大為75%,這個可以自行驗證。是以使用異或運算時,隻要32位hashcode的一位發生變化,最終的結果就會改變,盡可能的減少碰撞
  • 上面的總結下來就是,它們最終可以使哈希函數的輸入更加随機不同,使得我們哈希函數的輸出的範圍也更加均勻,降低了哈希沖突的機率
關于hash()擾動函數具體參見:Java 集合(三)| Map和HashMap詳解 | 底層結構和源碼深入分析(JDK1.8)

5.為什麼JDK1.8中的HashMap要使用紅黑樹這個資料結構來存儲資料

  • 在JDK1.7中,用連結清單存儲有一個缺點,當資料很多的時候,哈希沖突也會很多,導緻連結清單中存儲的資料可能會很長,當我們存元素或取元素的時候如果存在連結清單,我們就要周遊連結清單,而連結清單的硬傷就是周遊慢,因為要移動指針,是以這會嚴重影響HashMap的存取效率
  • 是以JDK1.8中用了紅黑樹來優化,當連結清單長度大于8的時候,會将連結清單轉為紅黑樹,紅黑樹是二分查找,提高了查詢的效率

6.為什麼不直接使用紅黑樹,而是在達到一定的門檻值後才轉化

  • 因為紅黑樹是一種二叉平衡樹,而且又是二叉平衡樹的改進,主要是在增删方面,為了保持樹的平衡,在增删資料後可能需要進行變色和旋轉的操作,這種保持平衡是需要付出代價的,這個代價展現在紅黑樹節點的大小約是連結清單節點的兩倍,但是在連結清單很長的情況下,該代價所損耗的資源要比周遊線性連結清單少,是以當連結清單長度大于8時,會考慮使用紅黑樹存儲,而連結清單長度很短的情況下,根本用不着轉化,轉化了反而會使存取效率降低

7.為什麼連結清單轉化為紅黑樹的門檻值為8,紅黑樹轉化為連結清單的門檻值為6

  • 由于紅黑樹節點的大小大約是正常連結清單節點的兩倍,是以我們僅在容器包含足夠的節點以保證使用時才使用它們轉化為紅黑樹,而門檻值為8是經過科學的分析計算了的,根據源碼中的解釋,理想情況下,由于存在擴容機制,我們使用随機的哈希碼,節點分布在 hash 桶中的頻率遵循泊松分布,按照泊松分布的公式計算,連結清單中節點個數為8時的機率為 0.00000006,這個機率十分小。并且連結清單長度到8時,紅黑樹此時的操作效率要高于連結清單,這個可根據時間複雜度公式來計算驗證
  • 至于紅黑樹轉化為連結清單的門檻值為什麼是6,中間有個內插補點7可以防止連結清單和樹之間頻繁的轉換。假設一下,如果設計成連結清單個數超過8則連結清單轉換成樹結構,連結清單個數小于8則樹結構轉換成連結清單,如果一個HashMap不停的插入、删除元素,連結清單個數在8左右徘徊,就會頻繁的發生樹轉連結清單、連結清單轉樹,效率會很低

8.HashMap中哈希表的長度為什麼要設計成2的幂次方

  • 一是當哈希數組的長度為2n時,哈希函數中的取模運算可以轉化成位運算:(n-1)&hash(其中,n為哈希表長度,hash為hash()計算結果),提高了運算效率
  • 二是在保證了哈希表長度為2n時,在HashMap 擴容後,同一個索引位置的節點重新hash分布後,最終的索引隻會分布在新哈希表的兩個位置:“原索引位置” 和 “(原索引 + 舊哈希表容量)位置”
  • 三是在進行哈希函數運算時,如果長度為2n,則長度-1的二進制必定是全1的形式,這樣可以保證hash()值所有位都能參與位運算,而如果不是2n,那麼其長度-1轉化為二進制時其末尾一位必定是0,那麼hash()值的最後一位就失去了意義,因為任何數和0相與(&),其結果都會是0,這時會增大hash沖突發生的機率
第二條具體原因參見:Java 集合(三)| Map和HashMap詳解 | 底層結構和源碼深入分析(JDK1.8)

9.HashMap中的key和value可以為null嗎

  • 都允許為null,因為當key為null時hash()方法得到的結果永遠為0,經過hash函數定位到數組中位置也是固定的,是以隻能有一個key值為null,當多個key的值為null同樣會觸發覆寫操作

10.除了key和value能為null,HashMap還有什麼特點

  • 線程不安全:沒有加同步鎖,多線程下建議用ConcurrentHashMap
  • 無序:因為是根據key的hash算法來計算在哈希表中的位置,是随機無序的
  • 存儲位置随時間變化:當觸發了擴容機制時,就會有節點的重hash分布,使得元素的存儲位置會随着容量的不斷增長而變化

11.為什麼String、Integer這樣的包裝類适合作HashMap的鍵

  • final類型,不可變,保證了插入和擷取時都是同一個hash碼,定位到同一個位置
  • String内部會維護一個hash緩存,在第一次使用時才會計算,進而避免每次計算
  • 它們都重寫了equals和hashCode方法,保證了不同的對象,它們的hashcode也是不同的,降低了hash沖突發生的機率
不完美、不遵守規範的重寫equals和hashCode方法,可能會造成不同的對象的hashcode相同,這會使hash沖突加劇,影響HashMap存取效率和正确性

12.為什麼重寫了equasl方法還要重寫hashCode方法

  • hashcode是為hash存儲的系統如HashMap服務的,在HashMap中,通過一個key的hashCode經過一系列運算來決定該key在哈希表中的位置,如果兩個不equals的對象有着相同的hashcode,那麼就會定位到哈希表的同一位置,造成hash沖突,當資料量多的情況下,會嚴重影響存取效率
  • 是以java有規定,兩個equals的對象必須有相同的hashcode,而兩個不equals的對象不要求必須産生不同的hashcode,但是我們應當知道,不equals卻有相同的hashcode會降低哈希表的性能,是以我們應當保證重寫equals方法有必要重寫hashCode方法,使其滿足java的規範,降低hash沖突
關于此問題詳細參考:Java 中hashCode() 、equals() 和==深入解析

13.說一說HashMap、HashTable、TreeMap、LinkedHashMap的關系

這4個都是Map大家族的,先看一下他們的繼承圖解:

Java 集合(五)| 集合面試彙總一、HashMap面試彙總二、List面試彙總三、補充

HashTable

  • HashTable是JDK1.0提供的,與Vector、Enumeration屬于最早的一批動态數組的實作類,後來為了将其繼續儲存下來,是以讓其多實作了一個Map接口
  • 關于他和HashMap的差別:HashTable是線程安全的,加了同步鎖,但key和value不能為null。而且HasTable繼承自Dictionary類,HashMap繼承自AbstractMap類
  • 現在理論上很少使用

LinkedHashMap

  • HashMap雖然是Map集合常用的一個子類,但其本身儲存的資料是無序的,如果希望Map集合中儲存的順序為其添加順序,那麼就可以考慮使用LinkedHashMap
  • 通過head和tail來維護有序雙向連結清單,通過Entry的after和before來屬性維護節點順序
  • 它繼承了HashMap

TreeMap

  • 通過實作Comparator實作自定義順序的Map,如果沒有指定Comparator,則會按key的升序排序,如果key沒有實作Comparable接口,則會抛出異常
  • 當我們需要自定義排序時可以使用此Map

HashMap

  • 最常用的Map結構
  • 是LinkedHashMap的父類
  • 無序、非線程安全、key-value允許為null

14.與JDK1.7相比,JDK1.8對HashMap作了哪些優化

  • 底層資料結構從“數組+連結清單”改成“數組+連結清單(+紅黑樹)”,主要是當hash 沖突較嚴重時,優化了連結清單過長的查找性能:O(n) -> O(logn)
  • 插入方式從“頭插法”改成“尾插法”,避免了JDK1.7在并發下的死循環
  • 擴容時計算節點在新表的索引位置方式從“h & (length-1)”改成“hash & oldCap”,更加簡潔巧妙
  • hash()方法的計算方式上也優化了:JDK1.7進行了多次擾動處理:用了多次移位位運算和異或(看起來很複雜,然而隻是為了增大随機性),而JDK1.8隻用了2次擾動處理:1次移位運算+1次異或。
// JDK 1.7.0
static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
// JDK 1.8.0_191
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
           
  • 優化了table初始化容量的計算方式(保證容量一定是2的幂次方):JDK1.7是從1開始不斷向左進行移位運算,直到找到大于等于入參容量的值;新的方式則是通過“5個移位+或等于運算”來計算。
// JDK 1.7.0
public HashMap(int initialCapacity, float loadFactor) {
    // 省略
    // Find a power of 2 >= initialCapacity
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;
    // ... 省略
}
// JDK 1.8.0_191
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
           

15.HashMap的擴容機制是怎樣的?JDK1.7和JDK1.8有什麼不同

  • JDK1.7的擴容條件是:當哈希桶數組的長度大于擴容門檻值且目前key值定位到的位置存在hash沖突,就會發生擴容
void addEntry(int hash, K key, V value, int bucketIndex) {
     //數組長度大于門檻值且存在哈希沖突(即目前數組下标有元素),就将數組擴容至2倍
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        createEntry(hash, key, value, bucketIndex);
    }
           
  • JDK1.8的擴容條件是:當哈希桶數組的長度大于擴容門檻值或連結清單轉紅黑樹時目前數組的長度小于64時就會發生擴容
//數組長度大于門檻值,就擴容
if (++size > threshold)
      resize();

//連結清單轉為紅黑樹時,若此時數組長度小于64,擴容數組
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
      resize();
           

16.HashMap和HashTable有什麼差別

  • HashTable加了同步鎖,是線程安全的,HashMap非線程安全,是以效率相對要高些
  • HashTable的鍵和值均不能為null,HashMap的鍵和值均可以為null

二、List面試彙總

1.ArrayList和LinkedList的差別和适用場景

  • ArrayList内部用的動态數組實作,LinkedList内部用的雙向循環連結清單實作,是以他們的使用場景是不同的
  • 對于元素的随機通路,ArrayList優于LinkedList,因為LinkedList要移動指針;對于元素要頻繁新增和删除,LinkedList要優于ArrayList,因為ArrayList要移動資料
  • 對 ArrayList 和 LinkedList 而言,在清單末尾增加一個元素所花的開銷都是固定的。對 ArrayList 而言,主要是在内部數組中增加一項,指向所添加的元素,偶爾可能會導緻對數組重新進行配置設定;而對 LinkedList 而言,這個開銷是統一的,配置設定一個内部 Entry 對象
  • 在 ArrayList 的中間插入或删除一個元素意味着這個清單中剩餘的元素都會被移動;而在 LinkedList 的中間插入或删除一個元素的開銷是固定的
  • LinkedList 不支援高效的随機元素通路。
  • ArrayList 的空間浪費主要展現在在 list 清單的結尾預留一定的容量空間(擴容機制決定),而 LinkedList 的空間花費則展現在它的每一個元素都需要消耗相當的空間。
  • 可以這樣說:當操作是在一列資料的後面添加資料而不是在前面或中間,并且需要随機地通路其中的元素時,使用ArrayList 會提供比較好的性能;當你的操作是在一列資料的前面或中間添加或删除資料,并且按照順序通路其中的元素時,就應該使用LinkedList了。

2.ArrayList自動擴容流程是怎樣的

  • 當我們在add元素時,當容量不足時,便會進行自動擴容
  • 首先看add元素後,ArrayList現在的長度是否超過了現在的最大容量
  • 超過了便會觸發擴容,一般情況下會将新容量設定為舊容量的1.5倍
  • 如果擴容後的新容量還是小于所需容量,那麼就會将新容量設定為所需的最小容量
  • 如果擴容後的新容量大于MAX_ARRAY_SIZE,那麼就将所需的最小容量和MAX_ARRAY_SIZE比較,如果小于,則新容量設定為MAX_ARRAY_SIZE,否則新容量設定為Integer.MAX_VALUE(其中,MAX_ARRAY_SIZE=Integer.MAX_VALUE-8)

3.ArrayList如何删除元素的

  • ArrayList的删除有根據下标删除和根據指定元素删除兩個方法,但第二種方法最終還是根據下标删除
  • 首先根據要删除元素的下标确定要移動的元素個數,如果為0說明直接删除末尾,如果大于0,則要調用 System.arraycopy()方法
  • System.arraycopy()方法是數組的拷貝,這裡用作删除的原理是将待删除元素後面的所有元素向前移動一位,使其覆寫待删除元素,最後将最後一位元素置為null,size數減一,删除完成

下面的圖幫助了解一下:

Java 集合(五)| 集合面試彙總一、HashMap面試彙總二、List面試彙總三、補充

4.ArrayList的預設初始化容量是多少?如何進行的初始化

  • 當我們構造ArrayList對象沒有指定容量大小時(即調用無參構造),就會設定預設的初始化容量為10,但此時構造出的ArrayList的容量還是0,隻有當我們第一次調用add方法時,會進行擴容處理,這時才會将ArrayList的容量設定為10
  • 當我們構造ArrayList對象指定容量大小時,且容量大于0,那麼就會生成指定容量大小的ArrayList
  • 當我們構造ArrayList對象指定容量大小時,且容量等于0,那麼會建立空容量的ArrayList
  • 當我們構造ArrayList對象指定容量大小時,且容量小于0,會報異常

5.ArrayList是線程安全的嗎

  • ArrayList不是線程安全的,隻能用在單線程環境下
  • 多線程環境下可以考慮用Collections.synchronizedList(List l)函數傳回一個線程安全的ArrayList類,也可以使用concurrent并發包下的CopyOnWriteArrayList類

三、補充

1.List 和 Map、Set 的差別

  • 結構特點:List 和 Set 是存儲單列資料的集合,Map 是存儲鍵和值這樣的雙列資料的集合;List 中存儲的資料是有順序,并且允許重複;Map 中存儲的資料是沒有順序的,其鍵是不能重複的,它的值是可以有重複的,Set 中存儲的資料是無序的,且不允許有重複,但元素在集合中的位置由元素的 hashCode 決定,位置是固定的(Set 集合根據 hashCode 來進行資料的存儲,是以位置是固定的,但是位置不是使用者可以控制的,是以對于使用者來說 Set 中的元素還是無序的);
  • 實作類:List 接口下的實作類(LinkedList:基于連結清單實作,連結清單記憶體是散亂的,每一個元素存儲本身記憶體位址的同時還存儲下一個元素的位址。連結清單增删快,查找慢;ArrayList:基于數組實作,非線程安全的,效率高,便于索引,但不便于插入删除;Vector:基于數組實作,線程安全的,效率低)。Map 接口下的實作類(HashMap:基于 hash 表的 Map 接口實作,非線程安全,高效,支援 null 值和 null 鍵;Hashtable:線程安全,低效,不支援 null 值和 null 鍵;LinkedHashMap:是HashMap 的一個子類,儲存了記錄的插入順序;SortedMap 接口:TreeMap,能夠把它儲存的記錄根據鍵排序,預設是鍵值的升序排序)。Set 接口下的實作類(HashSet:底層是由 HashMap 實作,不允許集合中有重複的值,使用該方式時需要重寫 equals()和 hashCode()方法;LinkedHashSet繼承與 HashSet,同時又基于LinkedHashMap 來進行實作,底層使用的是LinkedHashMp)。
  • 差別:List集合中對象按照索引位置排序,可以有重複對象,允許按照對象在集合中的索引位置檢索對象,例如通過list.get(i)方法來擷取集合中的元素;Map中的每一個元素包含一個鍵和一個值,成對出現,鍵對象不可以重複,值對象可以重複;Set集合中的對象不按照特定的方式排序,并且沒有重複對象,但它的實作類能對集合中的對象按照特定的方式排序,例如 TreeSet類,可以按照預設順序,也可以通過實作 java.util.Comparator接口來自定義排序方式。

2.并發集合和普通集合的差別

  • 并發集合常見的有ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque等。并發集合位于java.util.concurrent包下,是jdk1.5之後才有的,在 java 中有普通集合、同步(線程安全)的集合、并發集合。
  • 普通集合通常性能最高,但是不保證多線程的安全性和并發的可靠性。線程安全集合僅僅是給集合添加了 synchronized 同步鎖,嚴重犧牲了性能,而且對并發的效率就更低了,并發集合則通過複雜的政策不僅保證了多線程的安全又提高的并發時的效率。
并發集合拓展:ConcurrentHashMap 是線程安全的 HashMap 的實作,預設構造同樣有 initialCapacity 和 loadFactor 屬性, 不過還多了一個 concurrencyLevel 屬性,三屬性預設值分别為 16、0.75 及 16。其内部使用鎖分段技術,維持這鎖Segment 的數組,在 Segment 數組中又存放着 Entity[]數組,内部 hash 算法将資料較均勻分布在不同鎖中。put 操作:并沒有在此方法上加上 synchronized,首先對 key.hashcode 進行 hash 操作,得到 key 的 hash 值。hash 操作的算法和map 也不同,根據此hash 值計算并擷取其對應的數組中的Segment 對象(繼承自ReentrantLock),接着調用此 Segment 對象的 put 方法來完成目前操作。ConcurrentHashMap 基于 concurrencyLevel 劃分出了多個 Segment 來對 key-value 進行存儲,進而避免每次 put 操作都得鎖住整個數組。在預設的情況下,最佳情況時可允許 16 個線程并發無阻塞的操作集合對象,盡可能地減少并發時的阻塞現象。get(key)首先對 key.hashCode 進行 hash 操作,基于其值找到對應的 Segment 對象,調用其 get 方法完成目前操作。而 Segment 的 get 操作首先通過 hash 值和對象數組大小減1的值進行按位與操作來擷取數組上對應位置的HashEntry。在這個步驟中,可能會因為對象數組大小的改變,以及數組上對應位置的 HashEntry 産生不一緻性,那麼 ConcurrentHashMap 是如何保證的?對象數組大小的改變隻有在 put 操作時有可能發生,由于 HashEntry 對象數組對應的變量是 volatile 類型的,是以可以保證如 HashEntry 對象數組大小發生改變,讀操作可看到最新的對象數組大小。在擷取到了 HashEntry 對象後,怎麼能保證它及其 next 屬性構成的連結清單上的對象不會改變呢?這點ConcurrentHashMap 采用了一個簡單的方式,即 HashEntry 對象中的 hash、key、next 屬性都是 final 的,這也就意味着沒辦法插入一個 HashEntry 對象到基于 next 屬性構成的連結清單中間或末尾。這樣就可以保證當擷取到 HashEntry 對象後,其基于 next 屬性建構的連結清單是不會發生變化的。ConcurrentHashMap 預設情況下采用将資料分為 16 個段進行存儲,并且 16 個段分别持有各自不同的鎖Segment,鎖僅用于 put 和 remove 等改變集合對象的操作,基于 volatile 及 HashEntry 連結清單的不變性實作了讀取的不加鎖。這些方式使得 ConcurrentHashMap 能夠保持極好的并發支援,尤其是對于讀遠比插入和删除頻繁的 Map 而言,而它采用的這些方法也可謂是對于 Java 記憶體模型、并發機制深刻掌握的展現。

3.關于fast-fail機制

“fast-fail”的解釋:

In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system’s state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.

即:在系統設計中,快速失效系統一種可以立即報告任何可能表明故障的情況的系統。快速失效系統通常設計用于停止正常操作,而不是試圖繼續可能存在缺陷的過程。這種設計通常會在操作中的多個點檢查系統的狀态,是以可以及早檢測到任何故障。快速失敗子產品的職責是檢測錯誤,然後讓系統的下一個最進階别處理錯誤。

  • 在Java集合類中很多地方都用到了該機制進行設計,一旦使用不當,觸發fail-fast機制設計的代碼,就會發生非預期情況。我們通常說的Java中的fail-fast機制,預設指的是Java集合的一種錯誤檢測機制。當多個線程對部分集合進行結構上的改變的操作時,有可能會觸發該機制時,之後就會抛出并發修改異常ConcurrentModificationException.當然如果不在多線程環境下,如果在foreach周遊的時候使用add/remove方法,也可能會抛出該異常
  • 在增強for循環中,集合周遊是通過iterator進行的,但是元素的add/remove卻是直接使用的集合類自己的方法。這就導緻iterator在周遊的時候,會發現有一個元素在自己不知不覺的情況下就被删除/添加了,就會抛出一個異常,用來提示可能發生了并發修改
當調用Iterator自身的remove方法時,不會觸發fast-fail,具體參見:Java 集合(四)| 各種常用集合類型的周遊輸出 | Iterator接口講解

繼續閱讀