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

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

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長度。

- 觸發(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ù)。
- Java 7采用數(shù)組+鏈表來實(shí)現(xiàn),而 Java 8 中的 ConcurrentHashMap 使用數(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)序列化。



