Java面試題系列(二)——Java集合

1. Collection 和 Collections

  • Collection是集合類的上級(jí)接口,繼承他的接口主要有Set 和List.
  • Collections是針對(duì)集合類的一個(gè)幫助類,他提供一系列靜態(tài)方法實(shí)現(xiàn)對(duì)各種集合的搜索、排序、線程安全化等操作。

2. 常用的集合

image.png
Collection 接口的接口對(duì)象的集合(單列集合) 
├——List 接口:元素按進(jìn)入先后有序保存,可重復(fù) 
│     ├ LinkedList 接口實(shí)現(xiàn)類,鏈表,插入刪除,沒有同步,線程不安全 
│     ├ ArrayList 接口實(shí)現(xiàn)類,數(shù)組, 隨機(jī)訪問,沒有同步,線程不安全 (Collections.synchronizedList(new ArrayList<>());copyOnWriteArrayList是線程安全的)
│     └ Vector 接口實(shí)現(xiàn)類 數(shù)組,同步,線程安全
│         └ Stack 是Vector類的實(shí)現(xiàn)類 
└——Set 接口:僅接收一次,不可重復(fù),并做內(nèi)部排序 
│     └HashSet 使用hash表(數(shù)組)存儲(chǔ)元素 
│         └ LinkedHashSet 鏈表維護(hù)元素的插入次序 
└ —————TreeSet 底層實(shí)現(xiàn)為二叉樹,元素排好序

Map 接口鍵值對(duì)的集合 (雙列集合) 
├———Hashtable 接口實(shí)現(xiàn)類,同步,線程安全 
├———HashMap 接口實(shí)現(xiàn)類,沒有同步,線程不安全 
│         ├ LinkedHashMap 雙向鏈表和哈希表實(shí)現(xiàn) 
│         └ WeakHashMap 
├ ——–TreeMap 紅黑樹對(duì)所有的key進(jìn)行排序 
└———IdentifyHashMap
image.png

3. ArrayList擴(kuò)容機(jī)制

  • 創(chuàng)建ArrayList對(duì)象時(shí),若未指定集合容量,集合默認(rèn)容量為0;
  • 當(dāng)集合對(duì)象調(diào)用add方法存儲(chǔ)數(shù)據(jù)時(shí),進(jìn)行初始化容量為10
  • 集合初始化后,再次調(diào)用add方法,先將集合擴(kuò)大1.5倍,如果仍然不夠,新長度為傳入集合大小。并調(diào)用Arrays.copyOf方法將elementData數(shù)組指向新的長度為擴(kuò)容后長度的內(nèi)存空間
  • 若使用addAll方法添加元素,則初始化大小為10和添加集合長度的較大值

4. 數(shù)組(Array)和列表(ArrayList)的區(qū)別

  • Array可以包含基本類型和對(duì)象類型,ArrayList只能包含對(duì)象類型。
  • Array大小是固定的,ArrayList的大小是動(dòng)態(tài)變化的。
  • ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。

5. HashSet如何保證元素唯一性

??HashSet集合的底層數(shù)據(jù)結(jié)構(gòu)是哈希表。哈希表的存儲(chǔ)依賴兩個(gè)方法:hashCode()和equals()。首先判斷對(duì)象的hashCode()哈希值是否相同:

  • 如果不同,就直接添加到集合中。
  • 如果相同,就繼續(xù)執(zhí)行equals()方法。
    • 如果equals()方法返回true,說明元素重復(fù)了。就不添加。
    • 如果equals()方法返回false,說明元素沒有重復(fù)的,就添加到集合中。

6. 為什么重寫equals還要重寫hashcode?

??equals()方法來自object對(duì)象,默認(rèn)比較的是對(duì)象的引用是否指向同一塊內(nèi)存地址,重寫的目的是為了比較兩個(gè)對(duì)象的value值是否相等。
??如果我們對(duì)一個(gè)對(duì)象重寫了euqals,意思是只要對(duì)象的成員變量值都相等那么euqals就等于true,但不重寫hashcode,那么我們?cè)賜ew一個(gè)新的對(duì)象,當(dāng)原對(duì)象.equals(新對(duì)象)等于true時(shí),兩者的hashcode卻是不一樣的,由此將產(chǎn)生了理解的不一致,如在存儲(chǔ)散列集合時(shí)(如Set類),將會(huì)存儲(chǔ)了兩個(gè)值一樣的對(duì)象,導(dǎo)致混淆,因此,就也需要重寫hashcode。
為了保證這種一致性,必須滿足以下兩個(gè)條件:

  • 當(dāng)obj1.equals(obj2)為true時(shí),obj1.hashCode() == obj2.hashCode()必須為true
  • 當(dāng)obj1.hashCode() == obj2.hashCode()為false時(shí),obj1.equals(obj2)必須為false

7. map的分類和常見的情況

java.util.Map:它有四個(gè)實(shí)現(xiàn)類,分別是HashMap、Hashtable、LinkedHashMap 和TreeMap.

  • Hashmap 是一個(gè)最常用的Map,它根據(jù)鍵的HashCode值存儲(chǔ)數(shù)據(jù),根據(jù)鍵可以直接獲取它的值,具有很快的訪問速度,遍歷時(shí),取得數(shù)據(jù)的順序是完全隨機(jī)的。HashMap最多只允許一條記錄的鍵為Null,允許多條記錄的值為 Null;HashMap不支持線程的同步,即任一時(shí)刻如果有多個(gè)線程同時(shí)寫HashMap,可能會(huì)導(dǎo)致數(shù)據(jù)的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
  • Hashtable與 HashMap類似,它繼承自Dictionary類,不同的是:它不允許記錄的鍵或者值為空;它支持線程的同步,即任一時(shí)刻只有一個(gè)線程能寫Hashtable,因此也導(dǎo)致了 Hashtable在寫入時(shí)會(huì)比較慢。
  • LinkedHashMap 是HashMap的一個(gè)子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時(shí),先得到的記錄肯定是先插入的.也可以在構(gòu)造時(shí)用帶參數(shù),按照應(yīng)用次數(shù)排序。在遍歷的時(shí)候會(huì)比HashMap慢,不過有種情況例外,當(dāng)HashMap容量很大,實(shí)際數(shù)據(jù)較少時(shí),遍歷起來可能會(huì)比LinkedHashMap慢,因?yàn)長inkedHashMap的遍歷速度只和實(shí)際數(shù)據(jù)有關(guān),和容量無關(guān),而HashMap的遍歷速度和他的容量有關(guān)。
  • TreeMap實(shí)現(xiàn)SortMap接口,能夠把它保存的記錄根據(jù)鍵排序,默認(rèn)是按鍵值的升序排序,也可以指定排序的比較器,當(dāng)用Iterator 遍歷TreeMap時(shí),得到的記錄是排過序的。
    小結(jié):一般情況下,我們用的最多的是HashMap,在Map中插入、刪除和定位元素,HashMap 是最好的選擇。但如果您要按自然順序或自定義順序遍歷鍵,那么TreeMap會(huì)更好。如果需要輸出的順序和輸入的相同,那么用LinkedHashMap可以實(shí)現(xiàn),它還可以按讀取順序來排列.

8. HashMap

  • 哈希表:根據(jù)關(guān)鍵碼值(key value)而直接進(jìn)行訪問的數(shù)據(jù)結(jié)構(gòu)。也就是說,它通過關(guān)鍵碼值映射到表中一個(gè)位置來訪問記錄,以加快查找速度。這個(gè)映射函數(shù)叫做散列函數(shù),存放記錄的數(shù)組叫做散列表。

  • 哈希:把任意長度的輸入通過哈希算法映射成固定長度的輸出。

  • 哈希沖突(無法避免):計(jì)算得到的哈希值相同。
    解決方法:
    1)開放定址法
    2)再哈希法:雙哈希法計(jì)算
    3)鏈址法:HashMap實(shí)現(xiàn)方式,next指針連接Node
    4)建立公共溢出區(qū):建立基本表和溢出表,哈希值相同的直接放到溢出表
    哈希算法要求:
    1)高效,能夠處理長文本
    2)不能逆推原文
    3)盡量分散,減少哈希沖突

  • HashMap

    • 每個(gè)數(shù)據(jù)單元為一個(gè)Node結(jié)構(gòu),包含key,value,hash,next四個(gè)字段
    • 采用懶加載機(jī)制,即在進(jìn)行put操作時(shí)才真正構(gòu)建table數(shù)組。
    • 初始長度為16,默認(rèn)負(fù)載因子為0.75,當(dāng)HashMap的長度達(dá)到16*0.75=12時(shí),就會(huì)觸發(fā)擴(kuò)容流程,每次擴(kuò)容為原來的2倍(碰撞的概率低)。
    • 允許第一個(gè)位置的key為空,允許value為空
    • 線程不安全,導(dǎo)致cpu100%:jdk7鏈表成環(huán),jdk8紅黑樹父子節(jié)點(diǎn)成環(huán)
    • JDK1.7:數(shù)組+鏈表;JDK1.8:數(shù)組+鏈表+紅黑樹(鏈表長度大于8且table大于64轉(zhuǎn)為紅黑樹,紅黑樹節(jié)點(diǎn)個(gè)數(shù)小于6轉(zhuǎn)為鏈表)


      image.png

      image.png
  • 紅黑樹(自平衡二叉查找樹)特性:
    1)每個(gè)結(jié)點(diǎn)是黑色或者紅色。
    2)根結(jié)點(diǎn)是黑色。
    3)每個(gè)葉子結(jié)點(diǎn)(NIL)是黑色。 [注意:這里葉子結(jié)點(diǎn),是指為空(NIL或NULL)的葉子結(jié)點(diǎn)!]
    4)如果一個(gè)結(jié)點(diǎn)是紅色的,則它的子結(jié)點(diǎn)必須是黑色的。
    5)每個(gè)結(jié)點(diǎn)到葉子結(jié)點(diǎn)NIL所經(jīng)過的黑色結(jié)點(diǎn)的個(gè)數(shù)一樣的。

  • HashMap的get流程:
    1)首先會(huì)判斷數(shù)組是否不等于null,或者數(shù)組的長度是否大于0,如果不滿足,就說明HashMap里沒有數(shù)據(jù),直接返回null。
    2)通過 hash & (table.length - 1)獲取該key對(duì)應(yīng)的數(shù)據(jù)節(jié)點(diǎn)的hash槽;
    3)判斷首節(jié)點(diǎn)是否為空,為空則直接返回空;
    4)再判斷首節(jié)點(diǎn).key是否和目標(biāo)值相同,相同則直接返回(首節(jié)點(diǎn)不用區(qū)分鏈表還是紅黑樹);首節(jié)點(diǎn).next為空,則直接返回空;
    5)首節(jié)點(diǎn)是樹形節(jié)點(diǎn),則進(jìn)入紅黑樹數(shù)的取值流程,并返回結(jié)果;
    6)否則就會(huì)進(jìn)入一個(gè)do while循環(huán)進(jìn)行查詢鏈表。并返回結(jié)果;

  • HashMap的put流程:
    1)如果table數(shù)組為空數(shù)組{},進(jìn)行數(shù)組填充(為table分配實(shí)際內(nèi)存空間),入?yún)閠hreshold
    2)如果key為null時(shí)被放在了tab下標(biāo)為0的位置.
    3)根據(jù)hash值來確認(rèn)存放的位置。如果當(dāng)前位置是空直接添加到table中
    4)如果在首結(jié)點(diǎn)與我們待插入的元素有相同的hash和key值,則先記錄。
    5)如果首結(jié)點(diǎn)的類型是紅黑樹類型,則按照紅黑樹方法添加該元素
    6)如果首結(jié)點(diǎn)類型為鏈表類型,遍歷到末尾時(shí),先在尾部追加該元素結(jié)點(diǎn)。當(dāng)遍歷的結(jié)點(diǎn)數(shù)目大于8時(shí),則采取樹化結(jié)構(gòu)。
    7)modCount++;如果集合在被遍歷期間如果內(nèi)容發(fā)生變化則++modCount,只能檢測(cè)并發(fā)修改的bug,不能保證線程安全(ABA,祥見CAS)
    8)當(dāng)結(jié)點(diǎn)數(shù)+1大于threshold時(shí),則進(jìn)行擴(kuò)容

9. Hashmap擴(kuò)容原理

??e.hash & oldCap,就是用于計(jì)算位置b到底是0還是1用的,只要其結(jié)果是0,則新散列下標(biāo)就等于原散列下標(biāo),否則新散列坐標(biāo)要在原散列坐標(biāo)的基礎(chǔ)上加上原table長度。


image.png
  • 觸發(fā)擴(kuò)容時(shí)機(jī):
    1)當(dāng)new完HashMap之后,第一次往HashMap進(jìn)行put操作的時(shí)候,首先會(huì)進(jìn)行擴(kuò)容。
    2)當(dāng)HashMap的使用的桶數(shù)達(dá)到總桶數(shù)*加載因子的時(shí)候會(huì)觸發(fā)擴(kuò)容;
    3)當(dāng)某個(gè)桶中的鏈表長度達(dá)到8進(jìn)行鏈表扭轉(zhuǎn)為紅黑樹的時(shí)候,會(huì)檢查總桶數(shù)是否小于64,如果總桶數(shù)小于64也會(huì)進(jìn)行擴(kuò)容;

10. Hashmap拓展問題

  • 為什么JDK1.8采用紅黑樹存儲(chǔ)Hash沖突的元素?
    紅黑樹本質(zhì)上是一棵二叉查找樹,但它在二叉查找樹的基礎(chǔ)上增加了著色和相關(guān)的性質(zhì)使得紅黑樹相對(duì)平衡,從而保證了紅黑樹的查找、插入、刪除的時(shí)間復(fù)雜度最壞為O(log n)。能夠加快檢索速率。

  • 為什么在長度小于8時(shí)使用鏈表,不一直使用紅黑樹?
    桶中元素的插入只會(huì)在hash沖突時(shí)發(fā)生,而hash沖突發(fā)生的概率較小,一直維護(hù)一個(gè)紅黑樹比鏈表耗費(fèi)資源更多,在桶中元素量較小時(shí)沒有這個(gè)必要。

  • 為什么要使用紅黑樹而不使用AVL樹?
    紅黑樹與AVL樹,在檢索的時(shí)候效率差不多,都是通過平衡來二分查找。但紅黑樹不像AVL樹一樣追求絕對(duì)的平衡,紅黑樹允許局部很少的不完全平衡,這樣對(duì)于效率影響不大,但省去了很多沒有必要的調(diào)平衡操作,AVL樹調(diào)平衡有時(shí)候代價(jià)較大,所以效率不如紅黑樹。

  • 為什么數(shù)組容量必須是2次冪?
    索引計(jì)算公式為i = (n - 1) & hash,如果n為2次冪,那么n-1的低位就全是1,而擴(kuò)容后只有一位差異,也就是多出了最左位的1,這樣在通過 (length-1) &hash的時(shí)候,只要hash對(duì)應(yīng)的最左邊的那一個(gè)差異位為0,就能保證得到的新的數(shù)組索引和老數(shù)組索引一致(高效的數(shù)據(jù)遷移,大大減少了之前已經(jīng)散列良好的老數(shù)組的數(shù)據(jù)位置重新調(diào)換),哈希值進(jìn)行與操作時(shí)可以保證低位的值不變,如果低位值為1,則表示該位置可以插入值,從而保證分布均勻,效果等同于hash%n,但是位運(yùn)算比取余運(yùn)算要高效的多。


    image.png
  • 為什么單鏈表轉(zhuǎn)為紅黑樹要求桶內(nèi)的元素個(gè)數(shù)大于8?
    當(dāng)hashCode離散性很好的時(shí)候,樹型bin用到的概率非常小,因?yàn)閿?shù)據(jù)均勻分布在每個(gè)bin中,幾乎不會(huì)有bin中鏈表長度會(huì)達(dá)到閾值。但是在隨機(jī)hashCode下,離散性可能會(huì)變差,然而JDK又不能阻止用戶實(shí)現(xiàn)這種不好的hash算法,因此就可能導(dǎo)致不均勻的數(shù)據(jù)分布。不過理想情況下隨機(jī)hashCode算法下所有bin中節(jié)點(diǎn)的分布頻率會(huì)遵循泊松分布,而一個(gè)bin中鏈表長度達(dá)到8個(gè)元素的概率為0.00000006,幾乎是不可能事件。
    同理,少于6就從紅黑樹轉(zhuǎn)回單鏈表是為了節(jié)省維護(hù)一個(gè)樹的資源消耗,而選擇6作為臨界值,是因理想情況下一個(gè)bin中元素個(gè)數(shù)達(dá)到6的概率是0.00001316,達(dá)到7的概率為0.00000094,二者跨度較大,可以減小樹和鏈表之間頻繁轉(zhuǎn)化的可能性。

  • 為什么jdk1.8將頭插法改成尾插法?
    JDK1.7中擴(kuò)容時(shí),每個(gè)元素的rehash之后,都會(huì)插入到新數(shù)組對(duì)應(yīng)索引的鏈表頭,所以這就導(dǎo)致原鏈表順序?yàn)锳->B->C,擴(kuò)容之后,rehash之后的鏈表可能為C->B->A,元素的順序發(fā)生了變化。在并發(fā)場(chǎng)景下,擴(kuò)容時(shí)可能會(huì)出現(xiàn)循環(huán)鏈表的情況。而JDK1.8從頭插入改成尾插入元素的順序不變,避免出現(xiàn)循環(huán)鏈表的情況。

11. ConcurrentHashMap

  • JDK1.7:Segment數(shù)組+HashEntry


    image.png

1)HashEntry中value,以及next(鏈表)都是 volatile 修飾的,保證了獲取時(shí)的可見性。
2)原理上來說:ConcurrentHashMap 采用了分段鎖技術(shù),其中 Segment 繼承于 ReentrantLock。不會(huì)像HashTable那樣不管是 put 還是 get 操作都需要做同步處理,理論上 ConcurrentHashMap 支持 CurrencyLevel (Segment 數(shù)組數(shù)量,默認(rèn)為16)的線程并發(fā)。每當(dāng)一個(gè)線程占用鎖訪問一個(gè) Segment 時(shí),不會(huì)影響到其他的 Segment。

  • JDK1.8:Node +CAS+Synchorized+volatile
  • 對(duì)比Java7 和Java8 的異同和優(yōu)缺點(diǎn)
    1)數(shù)據(jù)結(jié)構(gòu)不同
    • Java 7采用數(shù)組+鏈表來實(shí)現(xiàn),而 Java 8 中的 ConcurrentHashMap 使用數(shù)組 + 鏈表 + 紅黑樹
      2)并發(fā)度
    • Java 7 中,每個(gè) Segment 獨(dú)立加鎖,最大并發(fā)個(gè)數(shù)就是 Segment 的個(gè)數(shù),默認(rèn)是 16。
    • Java 8 中,鎖粒度更細(xì),理想情況下 table 數(shù)組元素的個(gè)數(shù)(也就是數(shù)組長度)就是其支持并發(fā)的最大個(gè)數(shù),并發(fā)度比之前有提高。
      3)保證并發(fā)安全的原理
    • Java 7 采用 Segment 分段鎖來保證安全,而 Segment 是繼承自 ReentrantLock。
    • Java 8 中放棄了 Segment 的設(shè)計(jì),采用 Node + CAS + synchronized+volatile 保證線程安全。
      4)遇到 Hash 碰撞
    • Java 7 在 Hash 沖突時(shí),會(huì)使用拉鏈法,也就是鏈表的形式。
    • Java 8 先使用拉鏈法,在鏈表長度超過一定閾值時(shí),將鏈表轉(zhuǎn)換為紅黑樹,來提高查找效率。
      5)查詢時(shí)間復(fù)雜度
    • Java 7 遍歷鏈表的時(shí)間復(fù)雜度是 O(n),n 為鏈表長度。
    • Java 8 如果變成遍歷紅黑樹,那么時(shí)間復(fù)雜度降低為 O(logn),n 為樹的節(jié)點(diǎn)個(gè)數(shù)。

12. 線程安全的Map

  • HashTable
  • SynchronizedMap:加了一個(gè)對(duì)象鎖,每次操作hashmap都需要先獲取這個(gè)對(duì)象鎖
  • ConcurrentHashMap:線程安全是通過cas+synchronized+volatile來實(shí)現(xiàn)的
  • ConcurrentSkipListMap: 通過跳表來實(shí)現(xiàn)的高并發(fā)容器并且這個(gè)Map是有序排序的,根據(jù)key來排序

13. HashMap和Hashtable 的區(qū)別

JDK 1.8 中 HashMap 和 Hashtable 主要區(qū)別如下:

  • 父類不同。HashMap繼承自AbstractMap;Hashtable繼承自Dictionary。
  • 線程安全性不同。HashMap線程不安全;Hashtable 中的方法是Synchronized的。
  • HashMap最多只允許一條記錄的鍵為Null,允許多條記錄的值為 Null;Hashtable鍵和值都不允許為空。
  • 默認(rèn)初始大小和擴(kuò)容方式不同。HashMap默認(rèn)初始大小16,容量必須是2的整數(shù)次冪,擴(kuò)容時(shí)將容量變?yōu)樵瓉淼?倍;Hashtable默認(rèn)初始大小11,擴(kuò)容時(shí)將容量變?yōu)樵瓉淼?倍加1。
  • 迭代器不同。HashMap的Iterator是fail-fast迭代器;Hashtable還使用了enumerator迭代器。
  • hash的計(jì)算方式不同。HashMap計(jì)算了hash值;Hashtable使用了key的hashCode方法。
  • 是否有contains方法。HashMap沒有contains方法;Hashtable包含contains方法,類似于containsValue。

14. TreeMap底層實(shí)現(xiàn)

  • TreeMap 的實(shí)現(xiàn)就是紅黑樹數(shù)據(jù)結(jié)構(gòu),也就說是一棵自平衡的排序二叉樹,這樣就可以保證快速檢索指定節(jié)點(diǎn)。
  • 紅黑樹的插入、刪除、遍歷時(shí)間復(fù)雜度都為O(lgN),所以性能上低于哈希表。但是哈希表無法提供鍵值對(duì)的有序輸出,紅黑樹因?yàn)槭桥判虿迦氲?,可以按照鍵的值的大小有序輸出。

15. HashMap和TreeMap

  • HashMap基于散列桶(數(shù)組和鏈表)實(shí)現(xiàn);TreeMap基于紅黑樹實(shí)現(xiàn)。
  • HashMap不支持排序;TreeMap默認(rèn)是按照Key值升序排序的,可指定排序的比較器,主要用于存入元素時(shí)對(duì)元素進(jìn)行自動(dòng)排序。
  • HashMap大多數(shù)情況下有更好的性能,尤其是讀數(shù)據(jù)。在沒有排序要求的情況下,使用HashMap。
  • 都是非線程安全。

16. Java中集合遍歷的方式

  • 經(jīng)典for循環(huán)方式:Set不會(huì)將元素存儲(chǔ)為基于索引的元素。 因此這種方法不能用于所有集合。
  • 迭代器
  • 加強(qiáng)的for循環(huán)for(Object item: class):加強(qiáng)for循環(huán)實(shí)際上在背后使用的是迭代器。
  • forEach方法:將迭代代碼封裝在集合本身中,因此程序員不必為迭代集合編寫代碼。
  • listNames.forEach(name -> System.out.println(name));

17. Iterator和ListIterator的區(qū)別

??迭代器定義:Iterator提供了統(tǒng)一遍歷操作集合元素的統(tǒng)一接口, Collection接口實(shí)現(xiàn)Iterable接口,每個(gè)集合都通過實(shí)現(xiàn)Iterable接口中iterator()方法返回Iterator接口的實(shí)例, 然后對(duì)集合的元素進(jìn)行迭代操作.

  • Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List。
  • Iterator對(duì)集合只能是前向遍歷,ListIterator既可以前向也可以后向。
  • ListIterator實(shí)現(xiàn)了Iterator接口,并包含其他的功能,比如:增加元素,替換元素,獲取前一個(gè)和后一個(gè)元素的索引等等。

18. 快速失敗(fail-fast)和安全失敗(fail-safe)的區(qū)別

  • 快速失?。╢ail—fast)
    在用迭代器遍歷一個(gè)集合對(duì)象時(shí),如果遍歷過程中對(duì)集合對(duì)象的內(nèi)容進(jìn)行了修改(增加、刪除、修改),則會(huì)拋出ConcurrentModificationException。如HashMap
    • 原理:迭代器在遍歷時(shí)直接訪問集合中的內(nèi)容,并且在遍歷過程中使用一個(gè) modCount 變量。集合在被遍歷期間如果內(nèi)容發(fā)生變化,就會(huì)改變modCount的值。每當(dāng)?shù)魇褂胔ashNext()/next()遍歷下一個(gè)元素之前,都會(huì)檢測(cè)modCount變量是否為expectedmodCount值,是的話就返回遍歷;否則拋出異常,終止遍歷。
    • 注意:這里異常的拋出條件是檢測(cè)到 modCount!=expectedmodCount 這個(gè)條件。如果集合發(fā)生變化時(shí)修改modCount值剛好又設(shè)置為了expectedmodCount值(ABA問題),則異常不會(huì)拋出。因此,不能依賴于這個(gè)異常是否拋出而進(jìn)行并發(fā)操作的編程,這個(gè)異常只建議用于檢測(cè)并發(fā)修改的bug。
    • 場(chǎng)景:java.util包下的集合類都是快速失敗的,不能在多線程下發(fā)生并發(fā)修改(迭代過程中被修改)。
  • 安全失?。╢ail—safe)
    采用安全失敗機(jī)制的集合容器,在遍歷時(shí)不是直接在集合內(nèi)容上訪問的,而是先復(fù)制原有集合內(nèi)容,在拷貝的集合上進(jìn)行遍歷。
    • 原理:由于迭代時(shí)是對(duì)原集合的拷貝進(jìn)行遍歷,所以在遍歷過程中對(duì)原集合所作的修改并不能被迭代器檢測(cè)到,所以不會(huì)觸發(fā)Concurrent Modification Exception。
    • 缺點(diǎn):基于拷貝內(nèi)容的優(yōu)點(diǎn)是避免了Concurrent Modification Exception,但同樣地,迭代器并不能訪問到修改后的內(nèi)容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發(fā)生的修改迭代器是不知道的。
    • 場(chǎng)景:java.util.concurrent包下的容器都是安全失敗,可以在多線程下并發(fā)使用,并發(fā)修改。
    • 總結(jié):快速失敗和安全失敗是對(duì)迭代器而言的。 快速失?。寒?dāng)在迭代一個(gè)集合的時(shí)候,如果有另外一個(gè)線程在修改這個(gè)集合,就會(huì)拋出ConcurrentModification異常,java.util下都是快速失敗。 安全失?。涸诘鷷r(shí)候會(huì)在集合二層做一個(gè)拷貝,所以在修改集合上層元素不會(huì)影響下層。在java.util.concurrent下都是安全失敗

19. 為什么集合類沒有實(shí)現(xiàn)Cloneable和Serializable接口?

??克隆(cloning)或者是序列化(serialization)的語義和含義是跟具體的實(shí)現(xiàn)相關(guān)的。因此,應(yīng)該由集合類的具體實(shí)現(xiàn)來決定如何被克隆或者是序列化。

  • 實(shí)現(xiàn)Serializable序列化的作用:將對(duì)象的狀態(tài)保存在存儲(chǔ)媒體中以便可以在以后重寫創(chuàng)建出完全相同的副本;按值將對(duì)象從一個(gè)應(yīng)用程序域發(fā)向另一個(gè)應(yīng)用程序域。
  • 實(shí)現(xiàn) Serializable接口的作用就是可以把對(duì)象存到字節(jié)流,然后可以恢復(fù)。要網(wǎng)絡(luò)傳輸就得轉(zhuǎn)為字節(jié)流,所以在分布式應(yīng)用中,就得實(shí)現(xiàn)序列化。如果你不需要分布式應(yīng)用,那就沒必要實(shí)現(xiàn)實(shí)現(xiàn)序列化。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 文章目錄集合容器概述什么是集合集合的特點(diǎn)集合和數(shù)組的區(qū)別使用集合框架的好處常用的集合類有哪些?List,Set,M...
    灬佐手邊閱讀 385評(píng)論 0 1
  • 什么是集合 集合框架:用于存儲(chǔ)數(shù)據(jù)的容器。 集合框架是為表示和操作集合而規(guī)定的一種統(tǒng)一的標(biāo)準(zhǔn)的體系結(jié)構(gòu)。 任何集合...
    Java__JJ閱讀 313評(píng)論 0 1
  • Java集合類可用于存儲(chǔ)數(shù)量不等的對(duì)象,并可以實(shí)現(xiàn)常用的數(shù)據(jù)結(jié)構(gòu)如棧,隊(duì)列等,Java集合還可以用于保存具有映射關(guān)...
    小徐andorid閱讀 2,089評(píng)論 0 13
  • 集合框架 JCF(Java collections framework),也就是java集合框架 包括: 集合(C...
    jection閱讀 2,420評(píng)論 0 6
  • 剛剛經(jīng)歷過秋招,看了大量的面經(jīng),順便將常見的Java集合??贾R(shí)點(diǎn)總結(jié)了一下,并根據(jù)被問到的頻率大致做了一個(gè)標(biāo)注。...
    dybaby閱讀 349評(píng)論 0 3

友情鏈接更多精彩內(nèi)容