42 道Java集合經(jīng)典面試題,陪伴學(xué)習(xí),共同優(yōu)秀
本篇文章是Java集合經(jīng)典面試題。
1、Java中常用的集合有哪些?
Java集合框架為不同類型的集合定義了大量接口。
Java類庫中的集合:
- ArrayList,可以動態(tài)增長和縮減的一個索引序列。
- LinkedList,可以在任意位置高效插入和刪除的一個有序序列。
- ArrayDeque,實(shí)現(xiàn)為循環(huán)數(shù)組的一個雙端隊(duì)列。
- HashSet,沒有重復(fù)元素的一個無序集合。
- TreeSet,有序集。
- EnumSet,包含枚舉類型值的集合。
- LinkedHashSet,可以記住元素插入次序的集合。
- PriorityQueue,允許高效刪除最小元素的集合。
- HashMap,存儲鍵值對的數(shù)據(jù)結(jié)構(gòu)。
- TreeMap,鍵有序的鍵值對集合。
- EnumMap,鍵是枚舉類型的鍵值對集合。
- LinkedHashMap,可以記住添加次序的鍵值對集合。
- WeakHashMap,這個集合中的鍵如果不在使用,就會被垃圾回收。
2、Collection 和 Collections 有什么區(qū)別?
(1)Collection是最基本的集合接口,Collection派生了兩個子接口list和set,分別定義了兩種不同的存儲方式。
(2)Collections是一個包裝類,它包含各種有關(guān)集合操作的靜態(tài)方法(對集合的搜索、排序、線程安全化等)。
此類不能實(shí)例化,就像一個工具類,服務(wù)于Collection框架。
3、為什么集合類沒有實(shí)現(xiàn) Cloneable 和 Serializable 接口?
集合類通常包含多個元素,這些元素的類型和數(shù)量可能會非常復(fù)雜。實(shí)現(xiàn)Cloneable和Serializable接口需要對這些元素的狀態(tài)進(jìn)行精確的控制和管理,這可能會導(dǎo)致代碼變得復(fù)雜且容易出錯。
序列化和克隆操作可能會消耗大量的計算資源,尤其是在處理大型集合時。不實(shí)現(xiàn)這些接口可以避免不必要的性能開銷。
序列化和克隆操作可能會引發(fā)安全問題,因?yàn)樗鼈冊试S對對象的深拷貝。如果不小心使用,可能會導(dǎo)致敏感信息的泄露或不當(dāng)訪問。
4、數(shù)組和集合有什么本質(zhì)區(qū)別?
數(shù)組的大小是固定的,一旦創(chuàng)建后就不能改變,而集合的大小是動態(tài)的,可以根據(jù)需要擴(kuò)展和縮減。
數(shù)組可以存儲基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,而集合只能存儲引用數(shù)據(jù)類型(對象)。
數(shù)組通常用于存儲同一類型的數(shù)據(jù),而集合可以存儲不同類型的數(shù)據(jù)。
數(shù)組的長度是不可變的,而集合的長度是可變的,這意味著可以在運(yùn)行時向集合中添加或刪除元素。
5、數(shù)組和集合如何選擇?
如果數(shù)據(jù)的大小是固定的,那么數(shù)組可能是一個更好的選擇,因?yàn)樗峁┝斯潭ù笮〉拇鎯臻g。相反,如果數(shù)據(jù)的大小可能會發(fā)生變化,那么集合可能更合適。
如果需要存儲基本數(shù)據(jù)類型,那么只能使用數(shù)組,如果需要存儲不同類型的數(shù)據(jù),集合可能更適合。
數(shù)組在訪問速度上通常比集合更快,因?yàn)樗鼈兛梢酝ㄟ^索引直接訪問元素。
集合提供了許多有用的方法,如add、remove、contains等,這些方法使得數(shù)據(jù)的操作更加方便。如果需要使用這些方法,那么集合可能是更好的選擇。
6、list與Set區(qū)別
(1)List簡介
實(shí)際上有兩種List:一種是基本的ArrayList,其優(yōu)點(diǎn)在于隨機(jī)訪問元素,另一種是LinkedList,它并不是為快速隨機(jī)訪問設(shè)計的,而是快速的插入或刪除。
- ArrayList:由數(shù)組實(shí)現(xiàn)的List。允許對元素進(jìn)行快速隨機(jī)訪問,但是向List中間插入與移除元素的速度很慢。
- LinkedList :對順序訪問進(jìn)行了優(yōu)化,向List中間插入與刪除的開銷并不大。隨機(jī)訪問則相對較慢。
還具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 這些方法 (沒有在任何接口或基類中定義過)使得LinkedList可以當(dāng)作堆棧、隊(duì)列和雙向隊(duì)列使用。
(2)Set簡介
Set具有與Collection完全一樣的接口,因此沒有任何額外的功能。實(shí)際上Set就是Collection,只是行為不同。這是繼承與多態(tài)思想的典型應(yīng)用:表現(xiàn)不同的行為。Set不保存重復(fù)的元素(至于如何判斷元素相同則較為負(fù)責(zé))
- Set : 存入Set的每個元素都必須是唯一的,因?yàn)镾et不保存重復(fù)元素。加入Set的元素必須定義equals()方法以確保對象的唯一性。Set與Collection有完全一樣的接口。Set接口不保證維護(hù)元素的次序。
- HashSet:為快速查找設(shè)計的Set。存入HashSet的對象必須定義hashCode()。
- TreeSet:保存次序的Set, 底層為樹結(jié)構(gòu)。使用它可以從Set中提取有序的序列。
(3)list與Set區(qū)別
List,Set都是繼承自Collection接口。
List特點(diǎn):元素有放入順序,元素可重復(fù) ,Set特點(diǎn):元素?zé)o放入順序,元素不可重復(fù),重復(fù)元素會覆蓋掉,(元素雖然無放入順序,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實(shí)是固定的,加入Set 的Object必須定義equals()方法 ,另外list支持for循環(huán),也就是通過下標(biāo)來遍歷,也可以用迭代器,但是set只能用迭代,因?yàn)樗麩o序,無法用下標(biāo)來取得想要的值。)
Set和List對比:
- Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變。
- List:和數(shù)組類似,List可以動態(tài)增長,查找元素效率高,插入刪除元素效率低,因?yàn)闀鹌渌匚恢酶淖儭?/li>
7、HashMap 和 Hashtable 有什么區(qū)別?
- Hashtable不允許鍵或值為null,否則會拋出NullPointerException異常。而HashMap可以存儲key和value為null的元素;
- Hashtable繼承自Dictionary類,HashMap繼承自AbstractMap類并實(shí)現(xiàn)了Map接口;
- Hashtable在創(chuàng)建時必須指定容量大小,且默認(rèn)大小為11。而HashMap可以在創(chuàng)建時不指定容量大小,系統(tǒng)會自動分配初始容量,并采用2倍擴(kuò)容機(jī)制;
- 迭代器 Iterator 對 Hashtable 是安全的,而 Iterator 對 HashMap 不是安全的,因?yàn)榈鞅辉O(shè)計為工作于一個快照上,如果在迭代過程中其他線程修改了 HashMap,則會拋出并發(fā)修改異常;
- Hashtable是線程安全的,而HashMap是非線程安全的。Hashtable通過在每個方法前加上synchronized關(guān)鍵字來保證線程安全性,而HashMap則沒有實(shí)現(xiàn)這種機(jī)制。
8、concurrentHashMap和HashTable有什么區(qū)別
concurrentHashMap融合了hashmap和hashtable的優(yōu)勢,hashmap是不同步的,但是單線程情況下效率高,hashtable是同步的同步情況下保證程序執(zhí)行的正確性。
concurrentHashMap鎖的方式是細(xì)粒度的。concurrentHashMap將hash分為16個桶(默認(rèn)值),諸如get、put、remove等常用操作只鎖住當(dāng)前需要用到的桶。
concurrentHashMap的讀取并發(fā),因?yàn)樽x取的大多數(shù)時候都沒有鎖定,所以讀取操作幾乎是完全的并發(fā)操作,只是在求size時才需要鎖定整個hash。
而且在迭代時,concurrentHashMap使用了不同于傳統(tǒng)集合的快速失敗迭代器的另一種迭代方式,弱一致迭代器。在這種方式中,當(dāng)iterator被創(chuàng)建后集合再發(fā)生改變就不會拋出ConcurrentModificationException,取而代之的是在改變時new新的數(shù)據(jù)而不是影響原來的數(shù)據(jù),iterator完成后再講頭指針替代為新的數(shù)據(jù),這樣iterator時使用的是原來的數(shù)據(jù)。
9、HashMap 的工作原理是什么?
(1)存儲
當(dāng)向HashMap中添加一個鍵值對時,首先會計算鍵(Key)的哈希值,這個哈希值將決定該鍵值對在內(nèi)部數(shù)組中的索引位置。然后,該鍵值對會被存儲在對應(yīng)索引的鏈表中。如果兩個不同的鍵擁有相同的哈希值,它們會被存儲在同一個索引位置,這種現(xiàn)象稱為哈希沖突。為了解決沖突,HashMap會在鏈表中維護(hù)這些具有相同哈希值的鍵值對。
(2)查找
當(dāng)需要獲取某個特定鍵對應(yīng)的值時,HashMap會再次計算該鍵的哈希值,并沿著對應(yīng)索引的鏈表查找匹配的鍵。一旦找到,就返回對應(yīng)的值。
(3)擴(kuò)容
隨著HashMap中元素的增加,為了防止性能下降,當(dāng)鏈表的長度超過一定閾值時,HashMap會進(jìn)行自動擴(kuò)容。這個過程涉及到創(chuàng)建一個新的、更大的數(shù)組,并將舊數(shù)組中的所有元素重新映射到新數(shù)組的索引上。這個過程也被稱為rehashing。
(4)數(shù)據(jù)結(jié)構(gòu)
HashMap的內(nèi)部結(jié)構(gòu)是一個Entry數(shù)組,每個Entry包含一個key-value鍵值對。這樣設(shè)計的目的是為了高效地存儲和檢索數(shù)據(jù)。
10、Hashmap什么時候進(jìn)行擴(kuò)容?
在初始化HashMap時,需要指定其初始容量和負(fù)載因子。負(fù)載因子是一個介于0到1之間的浮點(diǎn)數(shù),默認(rèn)值為0.75。
當(dāng)HashMap中的元素數(shù)量達(dá)到當(dāng)前容量乘以負(fù)載因子時,即滿足capacity * loadFactor條件時,就會觸發(fā)擴(kuò)容操作。
在擴(kuò)容過程中,HashMap會創(chuàng)建一個新的數(shù)組,這個新數(shù)組的容量是原來容量的兩倍。然后,它會重新計算每個鍵值對的哈希值,并將這些鍵值對重新映射到新數(shù)組的對應(yīng)位置上。這個過程可能會涉及到一些性能開銷,因?yàn)樗枰匦掠嬎愎V岛椭匦路峙湓亍?/p>
由于每次擴(kuò)容都需要重新計算哈希值并重新分配元素,這會帶來一定的性能開銷。因此,我們應(yīng)該盡量避免讓HashMap頻繁地進(jìn)行擴(kuò)容,以提高性能。
11、說一下 HashMap 的實(shí)現(xiàn)原理?
HashMap實(shí)現(xiàn)了Map接口,Map接口對鍵值對進(jìn)行映射。Map中不允許重復(fù)的鍵。Map接口有兩個基本的實(shí)現(xiàn),HashMap和TreeMap。TreeMap保存了對象的排列次序,而HashMap則不能。HashMap允許鍵和值為null。HashMap是非synchronized的,但collection框架提供方法能保證HashMap synchronized,這樣多個線程同時訪問HashMap時,能保證只有一個線程更改Map。
HashMap在JDK1.7中采用數(shù)組+鏈表的存儲結(jié)構(gòu)。
HashMap采取Entry數(shù)組來存儲key-value,每一個鍵值對組成了一個Entry實(shí)體,Entry類時機(jī)上是一個單向的鏈表結(jié)構(gòu),它具有next指針,指向下一個Entry實(shí)體,以此來解決Hash沖突的問題。
HashMap實(shí)現(xiàn)一個內(nèi)部類Entry,重要的屬性有hash、key、value、next。
JDK1.8中采用數(shù)據(jù)+鏈表+紅黑樹的存儲形式。當(dāng)鏈表長度超過閾值(8)時,將鏈表轉(zhuǎn)換為紅黑樹。在性能上進(jìn)一步得到提升。
12、為什么HashMap使用紅黑樹而不使用AVL樹
- AVL樹是更加嚴(yán)格的平衡,因此可以提供更快的查找速度,一般讀取查找密集型任務(wù),適用AVL樹;
- 紅黑樹更適合于插入修改密集型任務(wù),即更適合HashMap;
- 通常,AVL樹的旋轉(zhuǎn)比紅黑樹的旋轉(zhuǎn)更加難以平衡和調(diào)試。
深入理解紅黑樹與AVL樹:
- AVL以及紅黑樹是高度平衡的樹數(shù)據(jù)結(jié)構(gòu)。它們非常相似,真正的區(qū)別在于在任何添加/刪除操作時完成的旋轉(zhuǎn)操作次數(shù)。
- 兩種實(shí)現(xiàn)都縮放為a O(lg N),其中N是葉子的數(shù)量,但實(shí)際上AVL樹在查找密集型任務(wù)上更快:利用更好的平衡,樹遍歷平均更短。另一方面,插入和刪除方面,AVL樹速度較慢:需要更高的旋轉(zhuǎn)次數(shù)才能在修改時正確地重新平衡數(shù)據(jù)結(jié)構(gòu)。
- 在AVL樹中,從根到任何葉子的最短路徑和最長路徑之間的差異最多為1。在紅黑樹中,差異可以是2倍。
- 兩個都給O(log n)查找,但平衡AVL樹可能需要O(log n)旋轉(zhuǎn),而紅黑樹將需要最多兩次旋轉(zhuǎn)使其達(dá)到平衡(盡管可能需要檢查O(log n)節(jié)點(diǎn)以確定旋轉(zhuǎn)的位置)。旋轉(zhuǎn)本身是O(1)操作,因?yàn)槟阒皇且苿又羔槨?/li>
13、Java中的ConcurrentHashMap中為什么不能存儲null?
與HashMap一樣,ConcurrentHashMap也是一個基于散列的Map,但它使用了一種完全不同的加鎖策略來提供更高的并發(fā)性和伸縮性。ConcurrentHashMap并不是將每個方法都在同一個鎖上同步并使得每次只能有一個線程訪問容器,而是使用一種更細(xì)的加鎖機(jī)制來實(shí)現(xiàn)更大程度的共享,這種機(jī)制成為分段鎖。在這種機(jī)制中,任意數(shù)量的讀取線程可以并發(fā)地訪問Map,執(zhí)行讀取操作的線程和執(zhí)行寫入操作的線程可以并發(fā)地訪問Map,并且一定數(shù)量的寫入線程可以并發(fā)地修改Map。ConcurrentHashMap帶來的結(jié)果是,在并發(fā)訪問環(huán)境下將實(shí)現(xiàn)更高的吞吐量,而在單線程環(huán)境中只損失非常小的性能。
ConcurrentHashMap返回的迭代器具有弱一致性,而并非“及時失敗”。弱一致性的迭代器可以容忍并發(fā)的修改,當(dāng)創(chuàng)建迭代器時會遍歷已有的元素,并可以在迭代器被構(gòu)造后將修改操作反映給容器。ConcurrentHashMap返回的迭代器具有弱一致性,而并非“及時失敗”。弱一致性的迭代器可以容忍并發(fā)的修改,當(dāng)創(chuàng)建迭代器時會遍歷已有的元素,并可以在迭代器被構(gòu)造后將修改操作反映給容器。
14、Java8開始ConcurrentHashMap,為什么舍棄分段鎖?
ConcurrentHashMap的原理是引用了內(nèi)部的 Segment ( ReentrantLock ) 分段鎖,保證在操作不同段 map 的時候, 可以并發(fā)執(zhí)行, 操作同段 map 的時候,進(jìn)行鎖的競爭和等待。從而達(dá)到線程安全, 且效率大于 synchronized。
但是在 Java 8 之后, JDK 卻棄用了這個策略,重新使用了 synchronized+CAS。
Java 8 中的 ConcurrentHashMap 放棄了分段鎖,而是引入了 CAS 操作,即 Compare and Swap,利用原子性的操作和無鎖編程的思想,來實(shí)現(xiàn)并發(fā)寫入:采用一種樂觀鎖的方式,通過比較當(dāng)前值與期望值是否相等,來決定是否更新。這種方式避免了對整個數(shù)據(jù)結(jié)構(gòu)加鎖,提高了并發(fā)寫入時的性能和效率。
15、ConcurrentHashMap(JDK1.8)為什么要使用synchronized而不是如ReentranLock這樣的可重入鎖?
我想從下面幾個角度討論這個問題:
(1)鎖的粒度
首先鎖的粒度并沒有變粗,甚至變得更細(xì)了。每當(dāng)擴(kuò)容一次,ConcurrentHashMap的并發(fā)度就擴(kuò)大一倍。
(2)Hash沖突
JDK1.7中,ConcurrentHashMap從過二次hash的方式(Segment -> HashEntry)能夠快速的找到查找的元素。在1.8中通過鏈表加紅黑樹的形式彌補(bǔ)了put、get時的性能差距。JDK1.8中,在ConcurrentHashmap進(jìn)行擴(kuò)容時,其他線程可以通過檢測數(shù)組中的節(jié)點(diǎn)決定是否對這條鏈表(紅黑樹)進(jìn)行擴(kuò)容,減小了擴(kuò)容的粒度,提高了擴(kuò)容的效率。
16、set有哪些實(shí)現(xiàn)類?
其實(shí)面試官想聽到的不只是有哪些實(shí)現(xiàn)類,而是觸類旁通,比如它們的原理、對比、使用場景等。
(1)HashSet
- 基于散列表實(shí)現(xiàn)的Set集合,內(nèi)部的存儲結(jié)構(gòu)是哈希表,是線程不安全的
- 它的底層數(shù)據(jù)結(jié)構(gòu)是HashMap,因此它擁有快速的存取速度,是用的最多的實(shí)現(xiàn)類;
- HashSet不保證元素的迭代順序,也不保證該順序會隨著時間的推移保持不變;
- 允許使用null元素;
(2)TreeSet
- 基于紅黑樹(Red-Black tree)或者AVL樹等自平衡二叉查找樹實(shí)現(xiàn)的Set集合;
- TreeSet可以確保集合元素處于排序狀態(tài);
- 不允許插入null元素
TreeSet對元素進(jìn)行排序的方式:
- 元素自身具備比較功能,需要實(shí)現(xiàn)Comparable接口,并覆蓋compareTo方法;
- 元素自身不具備比較功能,需要實(shí)現(xiàn)Comparator接口,并覆蓋compare方法。
(3)鏈接散列集LinkedHashSet
- LinkedHashSet結(jié)合了哈希表和鏈表兩種數(shù)據(jù)結(jié)構(gòu),具有可預(yù)知的迭代順序;
- 它繼承自HashSet,但是又添加了一個雙向鏈表來維持插入的順序;
- LinkedHashSet的元素迭代順序是它們被插入的順序,或者最近訪問的順序。
HashSet和LinkedHashSet內(nèi)部使用哈希表來存儲元素,當(dāng)多個元素經(jīng)過哈希函數(shù)計算后產(chǎn)生同一個索引位置時,就會產(chǎn)生哈希沖突。為了解決哈希沖突,HashSet和LinkedHashSet使用鏈?zhǔn)缴⒘屑夹g(shù),即在哈希表每個索引位置上維護(hù)一個鏈表,將所有哈希值相同的元素存放在同一個鏈表中,從而實(shí)現(xiàn)快速查找和添加元素。
LinkedHashMap的常用方法包括:
- put(K key, V value):將一個鍵值對添加到鏈接散列集中;
- get(K key):返回一個鍵值對,如果鍵不存在則返回null;
- remove(K key):從鏈接散列集中刪除一個鍵值對;
- containsKey(K key):檢查一個鍵是否存在于鏈接散列集中;
- size():返回鏈接散列集中鍵值對的數(shù)量;
這些方法都是基于鏈表實(shí)現(xiàn)的,因此它們的時間復(fù)雜度都是O(1),其中n是Map中元素的數(shù)量。
(4)AbstractSet
這是一個抽象類,它為創(chuàng)建新的set實(shí)現(xiàn)提供了一個框架。它本身不能直接實(shí)例化,但可以通過繼承并實(shí)現(xiàn)其抽象方法來創(chuàng)建自定義的set實(shí)現(xiàn)類。
面試中能說出AbstractSet的肯定不多,面試加分項(xiàng)。
17、說一下HashSet的實(shí)現(xiàn)原理
HashSet底層使用的是數(shù)組加鏈表或者紅黑樹的數(shù)據(jù)結(jié)構(gòu)。在JDK1.8之前,主要是數(shù)組加鏈表的方式,而在JDK1.8及以后的版本中,為了優(yōu)化性能,引入了紅黑樹。
當(dāng)我們向HashSet中添加一個元素時,首先會計算該元素的哈希值,然后根據(jù)哈希值確定元素在內(nèi)部HashMap中的存儲位置。如果該位置為空,則直接存儲;如果不為空,則需要通過鏈表或紅黑樹來處理沖突。
在查找元素時,也是通過計算哈希值來確定位置,然后在對應(yīng)的鏈表或紅黑樹中進(jìn)行搜索。
HashSet的性能可以通過合理設(shè)置初始容量和負(fù)載因子來提高。一個較大的初始容量可以減少擴(kuò)容操作的頻率,而合適的負(fù)載因子可以平衡空間利用率和查找效率。
18、Set是如何保證元素不重復(fù)的?
HashSet內(nèi)部實(shí)際上是通過HashMap來實(shí)現(xiàn)的。當(dāng)向HashSet中添加一個元素時,它會調(diào)用該元素的hashCode()方法來計算其哈希值,然后根據(jù)這個哈希值決定元素在HashMap中的存儲位置。
如果有兩個元素具有相同的哈希值,那么它們會被視為同一個位置的候選者。為了區(qū)分這些具有相同哈希值的元素,HashSet還會使用equals()方法來比較它們是否相等。只有當(dāng)元素在HashMap中不存在時,它才會被添加到集合中。如果已經(jīng)存在,則不會重復(fù)添加,從而保證了Set集合中元素的唯一性。
19、HashMap和HashSet的區(qū)別
(1)先了解一下HashCode
Java中的集合有兩類,一類是List,一類是Set。
List:元素有序,可以重復(fù)。
Set:元素?zé)o序,不可重復(fù)。
要想保證元素的不重復(fù),拿什么來判斷呢?這就是Object.equals方法了。如果元素有很多,增加一個元素,就要判斷n次嗎?
顯然不現(xiàn)實(shí),于是,Java采用了哈希表的原理。哈希算法也稱為散列算法,是將數(shù)據(jù)依特定算法直接指定到一根地址上,初學(xué)者可以簡單的理解為,HashCode方法返回的就是對象存儲的物理位置(實(shí)際上并不是)。
這樣一來,當(dāng)集合添加新的元素時,先調(diào)用這個元素的hashcode()方法,就一下子能定位到他應(yīng)該放置的物理位置上。如果這個位置上沒有元素,他就可以直接存儲在這個位置上,不用再進(jìn)行任何比較了。如果這個位置上有元素,就調(diào)用它的equals方法與新元素進(jìn)行比較,想同的話就不存了,不相同就散列其它的地址。所以這里存在一個沖突解決的問題。這樣一來實(shí)際上調(diào)用equals方法的次數(shù)就大大降低了,幾乎只需要一兩次。
簡而言之,在集合查找時,hashcode能大大降低對象比較次數(shù),提高查找效率。
Java對象的equals方法和hashCode方法時這樣規(guī)定的:
相等的對象就必須具有相等的hashcode。
- 如果兩個對象的hashcode相同,他們并不一定相同。
- 如果兩個對象的hashcode相同,他們并不一定相同。
如果兩個Java對象A和B,A和B不相等,但是A和B的哈希碼相等,將A和B都存入HashMap時會發(fā)生哈希沖突,也就是A和B存放在HashMap內(nèi)部數(shù)組的位置索引相同,這時HashMap會在該位置建立一個鏈接表,將A和B串起來放在該位置,顯然,該情況不違反HashMap的使用規(guī)則,是允許的。當(dāng)然,哈希沖突越少越好,盡量采用好的哈希算法避免哈希沖突。
equals()相等的兩個對象,hashcode()一定相等;equals()不相等的兩個對象,卻并不能證明他們的hashcode()不相等。
(2)HashMap和HashSet的區(qū)別
20、TreeSet常用方法有哪些?
- add(Object obj):將一個對象添加到TreeSet中。
- remove(Object obj):從TreeSet中移除一個對象。
- pollFirst():返回TreeSet中的第一個對象,如果TreeSet為空則返回null。
- pollLast():返回TreeSet中的最后一個對象,如果TreeSet為空則返回null。
- size():返回TreeSet中元素的個數(shù)。
- isEmpty():判斷TreeSet是否為空。
- contains(Object obj):判斷一個對象是否在TreeSet中。
- addAll(Collection<? extends E> c):將一個Collection對象中的元素添加到TreeSet中。
- removeAll(Collection<? extends E> c):從TreeSet中移除一個Collection對象中的元素。
- retainAll(Collection<? extends E> c):保留一個Collection對象中的元素,并將它們添加到TreeSet中。
21、TreeMap 和 TreeSet 在排序時如何比較元素?
TreeMap 和 TreeSet 都是基于紅黑樹實(shí)現(xiàn)的,它們在排序時會使用元素的自然順序(如果元素實(shí)現(xiàn)了 Comparable 接口)或者比較器(如果構(gòu)造時提供了 Comparator)。
當(dāng)插入一個元素到 TreeSet 中時,它會按照指定的比較方式(自然順序或比較器)找到正確的位置來插入該元素,以保證集合中的元素是有序的。如果元素沒有實(shí)現(xiàn) Comparable 接口且沒有提供比較器,則無法進(jìn)行排序,編譯時會報錯。
TreeMap 會根據(jù)鍵來對元素進(jìn)行排序。當(dāng)插入一個鍵值對到 TreeMap 中時,它會根據(jù)鍵的自然順序或者提供的比較器來確定鍵值對在映射中的正確位置。同樣,如果鍵的類型沒有實(shí)現(xiàn) Comparable 接口且沒有提供比較器,則無法進(jìn)行排序。
22、ArrayList 和 LinkedList 的區(qū)別是什么?
ArrayList和LinkedList都是Java集合框架中的一部分,它們實(shí)現(xiàn)了List接口,用于存儲元素的動態(tài)數(shù)組。然而,它們在內(nèi)部實(shí)現(xiàn)、效率、以及操作特點(diǎn)上有一些顯著的區(qū)別。
(1)內(nèi)部實(shí)現(xiàn)
ArrayList是基于數(shù)組實(shí)現(xiàn)的,其內(nèi)部維護(hù)了一個動態(tài)數(shù)組來存儲元素。數(shù)組是一塊連續(xù)的內(nèi)存空間,因此ArrayList在隨機(jī)訪問元素時非常高效,時間復(fù)雜度為O(1)。然而,當(dāng)添加或刪除元素時,如果數(shù)組已滿或需要移動元素,可能需要進(jìn)行擴(kuò)容或數(shù)據(jù)移動操作,這可能會降低效率。
LinkedList則是基于鏈表實(shí)現(xiàn)的,其內(nèi)部元素是通過節(jié)點(diǎn)(Node)連接在一起的。每個節(jié)點(diǎn)包含實(shí)際的數(shù)據(jù)以及指向下一個和上一個節(jié)點(diǎn)的指針。因此,LinkedList在內(nèi)存中的存儲不是連續(xù)的。這種結(jié)構(gòu)使得LinkedList在添加或刪除元素時效率較高,因?yàn)橹恍枰薷南嚓P(guān)節(jié)點(diǎn)的指針,而不需要移動大量數(shù)據(jù)。然而,隨機(jī)訪問元素時,LinkedList需要從頭或尾開始遍歷,效率較低。
(2)效率
當(dāng)需要頻繁地進(jìn)行隨機(jī)訪問元素(如通過索引獲取元素)時,ArrayList通常比LinkedList更高效,因?yàn)锳rrayList可以直接通過索引定位到元素。
而在添加或刪除元素時,LinkedList通常比ArrayList更高效,因?yàn)長inkedList只需要修改相關(guān)節(jié)點(diǎn)的指針,而不需要移動其他元素。
(3)控件開銷
ArrayList的主要控件開銷在于需要在其內(nèi)部數(shù)組中預(yù)留一定的空間以存儲元素。當(dāng)元素數(shù)量超出當(dāng)前容量時,ArrayList會自動進(jìn)行擴(kuò)容,以適應(yīng)更多的元素。這種擴(kuò)容操作可能會導(dǎo)致一定的性能開銷。
LinkedList的主要控件開銷在于需要存儲每個節(jié)點(diǎn)的信息以及節(jié)點(diǎn)之間的指針信息。這增加了額外的內(nèi)存開銷,但使得在鏈表中間添加或刪除元素的操作變得高效。
(4)線程安全
ArrayList和LinkedList都不是線程安全的。如果在多線程環(huán)境下使用,需要手動進(jìn)行同步處理或者使用線程安全的集合類,如Collections.synchronizedList()或CopyOnWriteArrayList。
綜上所述,ArrayList和LinkedList各有其優(yōu)勢和適用場景。在選擇使用哪種集合時,應(yīng)根據(jù)具體的應(yīng)用需求和性能要求來做出決策。如果需要頻繁地進(jìn)行隨機(jī)訪問元素,ArrayList可能更合適;而如果需要頻繁地進(jìn)行添加或刪除元素的操作,LinkedList可能更合適。
23、ArrayList 和 Vector 的區(qū)別?
ArrayList和Vector都是基于數(shù)組實(shí)現(xiàn)的List集合類,它們提供了動態(tài)數(shù)組的功能,可以根據(jù)需要自動調(diào)整大小以存儲對象。
Vector的公共方法大多帶有synchronized關(guān)鍵字,確保了方法是同步的,因此Vector是線程安全的。而ArrayList沒有這樣的同步措施,所以它是線程不安全的。
由于Vector的方法是同步的,因此在多線程環(huán)境下會涉及鎖的獲取和釋放,這可能導(dǎo)致性能上的開銷。相比之下,ArrayList由于沒有額外的同步開銷,通常運(yùn)行得更快。
當(dāng)?shù)讓訑?shù)組容量不足以容納新元素時,ArrayList會在原有基礎(chǔ)上擴(kuò)展約0.5倍的容量,而Vector則擴(kuò)展一倍的容量。這意味著在頻繁進(jìn)行大量添加操作的情況下,ArrayList可能會有更高的效率。
24、隊(duì)列和棧是什么?有什么區(qū)別?
隊(duì)列先進(jìn)先出,棧先進(jìn)后出。
遍歷數(shù)據(jù)速度不同。
棧只能從頭部取數(shù)據(jù) 也就最先放入的需要遍歷整個棧最后才能取出來,而且在遍歷數(shù)據(jù)的時候還得為數(shù)據(jù)開辟臨時空間,保持?jǐn)?shù)據(jù)在遍歷前的一致性;
隊(duì)列則不同,他基于地址指針進(jìn)行遍歷,而且可以從頭或尾部開始遍歷,但不能同時遍歷,無需開辟臨時空間,因?yàn)樵诒闅v的過程中不影像數(shù)據(jù)結(jié)構(gòu),速度要快的多。
25、Queue和Deque的區(qū)別是什么?
Queue以及Deque都是繼承于Collection,Deque是Queue的子接口。
Queue是FIFO的單向隊(duì)列,Deque是雙向隊(duì)列。
Queue有一個直接子類PriorityQueue,而Deque中直接子類有兩個:LinkedList以及ArrayDeque。
PriorityQueue的底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組,而無邊界的形容,那么指明了PriorityQueue是自帶擴(kuò)容機(jī)制的。
ArrayDeque是無初始容量的雙端隊(duì)列,LinkedList則是雙向鏈表。
PriorityQueue可以作為堆使用,而且可以根據(jù)傳入的Comparator實(shí)現(xiàn)大小的調(diào)整,會是一個很好的選擇。ArrayDeque通常作為?;蜿?duì)列使用,但是棧的效率不如LinkedList高。LinkedList通常作為?;蜿?duì)列使用,但是隊(duì)列的效率不如ArrayQueue高。
26、在 Queue 中 poll()和 remove()有什么區(qū)別?
offer()和add()區(qū)別:
增加新項(xiàng)時,如果隊(duì)列滿了,add會拋出異常,offer返回false。
poll()和remove()區(qū)別:
poll()和remove()都是從隊(duì)列中刪除第一個元素,remove拋出異常,poll返回null。
peek()和element()區(qū)別:
peek()和element()用于查詢隊(duì)列頭部元素,為空時element拋出異常,peek返回null。
27、說說你對優(yōu)先隊(duì)列的理解
優(yōu)先隊(duì)列中的元素可以按照任意的順序插入,但會按照有序的順序獲取。
優(yōu)先隊(duì)列常用結(jié)構(gòu)是PriorityQueue和ArrayDeque。
也就是在調(diào)用remove時,總是刪除隊(duì)列中最小的元素。
優(yōu)先隊(duì)列使用堆作為存儲數(shù)據(jù)結(jié)構(gòu),堆是一個自組織的二叉樹,其添加和刪除操作會讓最小的元素移動到根,而不必花費(fèi)時間對元素進(jìn)行排序。
優(yōu)先隊(duì)列的主要用途是調(diào)度。每個任務(wù)有一個優(yōu)先級,任務(wù)以隨機(jī)順序插入到隊(duì)列中,每當(dāng)啟動一個新的任務(wù)時,將從隊(duì)列中刪除優(yōu)先級最高的任務(wù)。
28、說說你對雙端隊(duì)列的理解
雙端隊(duì)列是一種特殊的隊(duì)列,它的兩端都可以進(jìn)行插入和刪除操作。這種隊(duì)列的實(shí)現(xiàn)方式是使用兩個指針,一個指針指向隊(duì)列的頭部,另一個指針指向隊(duì)列的尾部。當(dāng)需要插入或刪除元素時,只需要移動指針即可。
雙端隊(duì)列的主要優(yōu)點(diǎn)是可以在隊(duì)列的兩端進(jìn)行操作,因此具有較高的效率。此外,雙端隊(duì)列還具有一些其他的優(yōu)點(diǎn),例如可以在隊(duì)列的兩端進(jìn)行查詢操作,因此具有較高的查詢效率。
雙端隊(duì)列的缺點(diǎn)是插入和刪除操作的時間復(fù)雜度都是O(1),因此在處理大量數(shù)據(jù)時可能會導(dǎo)致性能問題。此外,雙端隊(duì)列的空間復(fù)雜度也是O(1),因此在插入和刪除元素時需要使用額外的空間。
29、CopyOnWriteArrayList是什么,有哪些應(yīng)用場景?
CopyOnWriteArrayList是Java并發(fā)包java.util.concurrent下提供的一個線程安全的ArrayList實(shí)現(xiàn),它是寫時復(fù)制(Copy-On-Write)的容器。
CopyOnWriteArrayList的核心特性在于,當(dāng)修改容器(例如添加、刪除元素)時,不是直接修改當(dāng)前容器,而是先復(fù)制當(dāng)前容器的副本,然后在副本上進(jìn)行修改。修改完成后,再將原容器的引用指向新的容器。這種策略使得讀操作可以完全不用加鎖,因此讀取性能極高。同時,寫入操作也不會阻塞讀取操作,只有寫入和寫入之間需要進(jìn)行同步等待。
由于CopyOnWriteArrayList的這些特性,它特別適用于以下場景:
- 讀多寫少的場景:當(dāng)對數(shù)據(jù)的讀操作次數(shù)遠(yuǎn)遠(yuǎn)高于寫操作時,使用CopyOnWriteArrayList可以有效提升系統(tǒng)性能。因?yàn)槊看涡薷牟僮鞫紩?chuàng)建底層數(shù)組的副本,從而避免了讀取操作受到寫入操作的干擾。
- 數(shù)據(jù)更新要求不頻繁的場景:由于每次添加、修改或刪除列表中的元素時,CopyOnWriteArrayList都需要重新創(chuàng)建一個新的底層數(shù)組,因此在實(shí)現(xiàn)上會消耗更多的內(nèi)存空間。因此,它更適用于數(shù)據(jù)更新不頻繁的場景。
- 互斥訪問數(shù)據(jù)不方便的場景:在多線程環(huán)境下,如果需要對一個ArrayList實(shí)例進(jìn)行訪問,通常需要加鎖以保證數(shù)據(jù)一致性。但在某些場景下,加鎖可能會給程序帶來額外的復(fù)雜度和延遲。此時,可以考慮使用CopyOnWriteArrayList。
- 需要保證數(shù)據(jù)一致性的場景:由于每個線程都在自己的副本上進(jìn)行操作,因此不存在讀取過程中數(shù)據(jù)被其他線程修改的問題,從而保證了數(shù)據(jù)的一致性。
30、使用CopyOnWriteArrayList時需要注意哪些問題?
(1)內(nèi)存占用問題
由于CopyOnWriteArrayList的寫時復(fù)制機(jī)制,當(dāng)進(jìn)行寫操作時,內(nèi)存中會同時駐扎兩個對象的內(nèi)存,舊的對象和新寫入的對象。在復(fù)制時只復(fù)制容器里的引用,在寫時才會創(chuàng)建新對象添加到新容器里,而舊容器的對象還在使用,所以有兩份對象內(nèi)存。
(2)數(shù)據(jù)一致性問題
CopyOnWriteArrayList只能保證數(shù)據(jù)的最終一致性,不能保證數(shù)據(jù)的實(shí)時一致性。因?yàn)閺?fù)制和操作元素需要一些時間,所以會有延遲。如果希望寫入的數(shù)據(jù)馬上能讀到,要求數(shù)據(jù)強(qiáng)一致性的話,建議不要使用CopyOnWriteArrayList。
(3)線程安全
CopyOnWriteArrayList是寫同步,讀非同步的。多個線程對CopyOnWriteArrayList進(jìn)行寫操作是線程安全的,但是在讀操作時是非線程安全的。如果在for循環(huán)中使用下標(biāo)的方式去讀取數(shù)據(jù),可能會報錯ArrayIndexOutOfBoundsException。
(4)不支持add()、set()、remove()方法
CopyOnWriteArrayList的迭代器實(shí)現(xiàn)了ListIterator接口,但是add()、set()、remove()方法都直接拋出了UnsupportedOperationException異常,所以應(yīng)該避免使用迭代器的這幾個方法。
31、說一下鏈表的實(shí)現(xiàn)原理
從數(shù)組中間刪除一個元素開銷很大,其原因是向數(shù)組中插入元素時,此元素之后的所有元素都要向后端移動,刪除時也是,數(shù)組中位于被刪除元素之后的所有元素都要向數(shù)組的前端移動。
此時,在Java中,可以通過鏈表解決這個問題。
數(shù)組是在連續(xù)的存儲位置上存放對象引用,而鏈表則是將每個對象存放在單獨(dú)的鏈接link中。每個鏈接還存放著序列中下一個鏈接的引用。在Java中,所有的鏈表都是雙向鏈接,即每個鏈接還存儲前驅(qū)的引用。
在鏈表中新增、刪除一個元素是很輕松的操作,只需要更新鎖刪除元素前后對應(yīng)的鏈接即可。
有的同學(xué)可能覺得上面兩個圖,沒啥區(qū)別,其實(shí)就是前后鏈接指向的問題,so easy。
在Java中,可以使用雙指針法來向鏈表中間添加元素。
ListNode newNode = new ListNode(val);
if (head == null) {
head = newNode;
} else {
ListNode curr = head;
while (curr.next != null && curr.next.next != null) {
curr = curr.next;
}
curr.next = newNode;
}
在上面的代碼中,我們首先創(chuàng)建一個新的節(jié)點(diǎn)newNode,并將其插入到鏈表的中間。如果鏈表為空,則將新節(jié)點(diǎn)設(shè)置為頭部節(jié)點(diǎn)。否則,我們遍歷鏈表,找到最后一個節(jié)點(diǎn),并將新節(jié)點(diǎn)插入到該節(jié)點(diǎn)的后面。
32、說一下散列表的實(shí)現(xiàn)原理
如果想要查找某個元素,但又不知道它的存儲位置,此時,就需要遍歷所有元素,直到找到匹配的元素為止。如果集合中包含的元素很多,就需要耗費(fèi)很長時間時間。
此時,散列表閃亮登場。
散列表可以快速的查找對象,散列表為每個元素計算一個整數(shù),稱為散列碼,散列碼是以某種方式由對象的實(shí)例字段得出的一個整數(shù),可以保證不同的數(shù)據(jù)對象擁有不同的散列碼。
在Java中,刪列表實(shí)現(xiàn)為鏈表數(shù)組,每個列表被稱為桶bucket,可以通過:先計算散列碼,再與桶的總數(shù)取余,所得到的數(shù)就是保存這個元素的那個桶的索引。
可以通過初始化桶數(shù)的方式,快速的進(jìn)行元素插入。
如果裝載因子是0.75,當(dāng)表中已經(jīng)填到75%就會進(jìn)行自動再散列,新的桶數(shù)就是原來的兩倍。對大多數(shù)情況而言,裝載因子為0.75是比較合理的。
33、說說你對映射視圖的理解
Java中的映射視圖是指一種將Map轉(zhuǎn)換為Set或Collection的機(jī)制,以便于更方便地操作Map中的元素。
- keySet():返回包含Map中所有鍵的Set;
- values():返回包含Map中所有值的Collection;
- entrySet():返回包含Map中所有鍵值對的Set;
通過這些映射視圖,可以方便地遍歷Map中的元素、檢查某個鍵是否存在、刪除指定鍵等操作。
在使用時需要注意,映射視圖只是一個視圖,即對原Map進(jìn)行的修改會反映到相應(yīng)的映射視圖中,反之亦然,因此要謹(jǐn)慎使用。
另外,由于映射視圖是基于Map實(shí)現(xiàn)的,因此對映射視圖的修改也可能影響到原Map中的元素。
34、說說你對弱散列映射WeakHashMap的理解
Java中的弱散列映射指的是一種特殊的Map實(shí)現(xiàn),即WeakHashMap類。和普通HashMap不同,WeakHashMap中的鍵是弱引用,即當(dāng)某個鍵不再被外部對象引用時,該鍵及其對應(yīng)的值會被自動清除掉,以避免內(nèi)存泄漏問題。
通過使用WeakHashMap,可以將某些對象與其他應(yīng)用邏輯分離開來,使得它們的生命周期僅由其它對象的引用決定,當(dāng)沒有任何對象引用時,這些對象會被自動清除,從而釋放系統(tǒng)資源。在Java中,常用WeakHashMap來實(shí)現(xiàn)緩存、事件通知等場景。需要注意的是,由于弱引用的存在,WeakHashMap無法保證元素的順序,因此在遍歷時應(yīng)該謹(jǐn)慎。
WeakHashMap是一種基于紅黑樹實(shí)現(xiàn)的有序映射,它的常用方法包括:
- put(K key, V value):將一個鍵值對添加到弱散列映射中;
- get(K key):返回一個鍵值對,如果鍵不存在則返回null;
- remove(K key):從弱散列映射中刪除一個鍵值對;
- containsKey(K key):檢查一個鍵是否存在于弱散列映射中;
- size():返回弱散列映射中鍵值對的數(shù)量;
這些方法都是基于紅黑樹實(shí)現(xiàn)的,因此它們的時間復(fù)雜度都是O(log n),其中n是Map中元素的數(shù)量。
35、說說你對鏈接散列映射LinkedHashMap的理解
Java中的鏈接散列映射指的是HashMap和LinkedHashMap這兩個鍵值對映射集合實(shí)現(xiàn)類。它們都是基于哈希表實(shí)現(xiàn)的,鏈?zhǔn)缴⒘惺墙鉀Q哈希沖突的一種方法。
具體來說,HashMap和LinkedHashMap內(nèi)部使用哈希表來存儲鍵值對,當(dāng)多個鍵經(jīng)過哈希函數(shù)計算后產(chǎn)生同一個索引位置時,就會產(chǎn)生哈希沖突。為了解決哈希沖突,HashMap和LinkedHashMap使用鏈?zhǔn)缴⒘屑夹g(shù),即在哈希表每個索引位置上維護(hù)一個鏈表,將所有哈希值相同的鍵值對存放在同一個鏈表中,從而實(shí)現(xiàn)快速查找和添加元素。
HashMap和LinkedHashMap的區(qū)別在于,前者是無序鍵值對集合,而后者是有序鍵值對集合。具體來說,LinkedHashMap內(nèi)部使用一個雙向鏈表來維護(hù)鍵值對的插入順序,因此遍歷LinkedHashMap時可以按照鍵值對插入的順序進(jìn)行。需要注意的是,在使用HashMap和LinkedHashMap時,應(yīng)根據(jù)具體的業(yè)務(wù)需求和性能要求選擇合適的實(shí)現(xiàn)類。
36、說說你對LinkedHashSet的理解
Java中的鏈接散列集指的是HashSet和LinkedHashSet這兩個集合實(shí)現(xiàn)類。它們都是基于哈希表(Hash Table)實(shí)現(xiàn)的,鏈?zhǔn)缴⒘惺墙鉀Q哈希沖突的一種方法。
HashSet和LinkedHashSet內(nèi)部使用哈希表來存儲元素,當(dāng)多個元素經(jīng)過哈希函數(shù)計算后產(chǎn)生同一個索引位置時,就會產(chǎn)生哈希沖突。為了解決哈希沖突,HashSet和LinkedHashSet使用鏈?zhǔn)缴⒘屑夹g(shù),即在哈希表每個索引位置上維護(hù)一個鏈表,將所有哈希值相同的元素存放在同一個鏈表中,從而實(shí)現(xiàn)快速查找和添加元素。
HashSet和LinkedHashSet的區(qū)別在于,前者是無序集合,而后者是有序集合。具體來說,LinkedHashSet內(nèi)部使用一個雙向鏈表來維護(hù)元素的插入順序,因此遍歷LinkedHashSet時可以按照元素插入的順序進(jìn)行。需要注意的是,在使用HashSet和LinkedHashSet時,應(yīng)根據(jù)具體的業(yè)務(wù)需求和性能要求選擇合適的實(shí)現(xiàn)類。
LinkedHashMap的常用方法包括:
- put(K key, V value):將一個鍵值對添加到鏈接散列集中;
- get(K key):返回一個鍵值對,如果鍵不存在則返回null;
- remove(K key):從鏈接散列集中刪除一個鍵值對;
- containsKey(K key):檢查一個鍵是否存在于鏈接散列集中;
- size():返回鏈接散列集中鍵值對的數(shù)量;
這些方法都是基于鏈表實(shí)現(xiàn)的,因此它們的時間復(fù)雜度都是O(1),其中n是Map中元素的數(shù)量。
37、說說你對枚舉集EnumSet的理解
Java中的枚舉集指的是基于枚舉類型實(shí)現(xiàn)的集合類,即EnumSet。
它是一個專門用于存儲枚舉類型值的高效集合實(shí)現(xiàn)類,可以實(shí)現(xiàn)基本操作(如添加、刪除、查找等)和集合運(yùn)算(如交、并、補(bǔ)等),同時還提供了高性能的迭代器,可以按照枚舉類型常量在內(nèi)存中出現(xiàn)的順序進(jìn)行遍歷。
EnumSet使用位向量(bit vector)實(shí)現(xiàn),即將每個枚舉類型常量映射到一個二進(jìn)制位上,從而快速進(jìn)行集合運(yùn)算。由于EnumSet只能存儲枚舉類型值,因此它具有類型安全性、性能高效、空間利用率高等優(yōu)點(diǎn)。
EnumSet是一個抽象類,不能直接實(shí)例化,但可以通過EnumSet的靜態(tài)工廠方法創(chuàng)建實(shí)例,例如EnumSet.of()、EnumSet.range()等。此外,EnumSet也支持各種集合轉(zhuǎn)換操作,可以與其他集合實(shí)現(xiàn)類進(jìn)行互相轉(zhuǎn)換。
38、說說你對EnumMap的理解
Java中的枚舉映射指的是基于枚舉類型實(shí)現(xiàn)的鍵值對集合類,即EnumMap。它是一個專門用于存儲枚舉類型作為鍵的鍵值對集合實(shí)現(xiàn)類,可以實(shí)現(xiàn)基本操作(如添加、刪除、查找等)和集合運(yùn)算(如交、并、補(bǔ)等),同時還提供了高性能的迭代器,可以按照枚舉類型常量在內(nèi)存中出現(xiàn)的順序進(jìn)行遍歷。
EnumMap使用數(shù)組實(shí)現(xiàn),數(shù)組的長度等于枚舉類型常量數(shù)目,每個位置上存儲的是該枚舉類型常量所對應(yīng)的值。由于EnumMap只能存儲枚舉類型作為鍵,因此它具有類型安全性、性能高效、空間利用率高等優(yōu)點(diǎn)。
需要注意的是,EnumMap也是一個抽象類,不能直接實(shí)例化,但可以通過EnumMap的構(gòu)造方法或靜態(tài)工廠方法創(chuàng)建實(shí)例,例如new EnumMap<>(MyEnum.class)、EnumMap.copyOf()等。此外,EnumMap也支持各種集合轉(zhuǎn)換操作,可以與其他集合實(shí)現(xiàn)類進(jìn)行互相轉(zhuǎn)換。
39、Comparator與Comparable有什么區(qū)別
Comparator與Comparable在Java中都是用于比較對象大小的接口。
首先,從功能角度來看,Comparable是一個排序接口,當(dāng)一個類實(shí)現(xiàn)了Comparable接口時,它表示這個類的對象之間可以相互比較大小,即這個類支持排序。而Comparator則是一個比較器接口,它允許我們實(shí)現(xiàn)該接口,自定義比較算法,從而創(chuàng)建一個特定類的比較器來進(jìn)行排序??梢哉f,Comparable相當(dāng)于“內(nèi)部比較器”,而Comparator相當(dāng)于“外部比較器”。
其次,從使用靈活性來看,Comparable接口的耦合性相對較強(qiáng),通常用作類的默認(rèn)排序方法。這意味著如果我們需要對一個類的實(shí)例進(jìn)行排序,而該類的定義又沒有發(fā)生變化,我們通常會讓該類實(shí)現(xiàn)Comparable接口,并提供比較的邏輯。而Comparator接口的靈活性和擴(kuò)展性更優(yōu),它主要用于當(dāng)默認(rèn)排序不滿足需求時,提供自定義排序。例如,我們可能需要對一個已經(jīng)存在的類進(jìn)行排序,而這個類并沒有實(shí)現(xiàn)Comparable接口,或者我們想要對這個類進(jìn)行不同的排序,這時就可以使用Comparator接口。
最后,從方法定義上來看,Comparable接口定義了一個抽象方法compareTo(T o),用于比較當(dāng)前對象與另一個對象的大小。而Comparator接口定義了一個抽象方法compare(T o1, T o2),用于比較兩個對象的大小。
40、Iterator 怎么使用?有什么特點(diǎn)?
為了方便的處理集合中的元素,Java中出現(xiàn)了一個對象,該對象提供了一些方法專門處理集合中的元素.例如刪除和獲取集合中的元素.該對象就叫做迭代器(Iterator)。
Iterator 接口源碼中的方法:
- java.lang.Iterable 接口被 java.util.Collection 接口繼承,java.util.Collection 接口的 iterator() 方法返回一個 Iterator 對象
- next() 方法獲得集合中的下一個元素
- hasNext() 檢查集合中是否還有元素
- remove() 方法將迭代器新返回的元素刪除
41、Iterator 和 ListIterator 有什么區(qū)別?
ListIterator 繼承 Iterator。
ListIterator 比 Iterator多方法:
- add(E e) 將指定的元素插入列表,插入位置為迭代器當(dāng)前位置之前。
- set(E e) 迭代器返回的最后一個元素替換參數(shù)e。
- hasPrevious() 迭代器當(dāng)前位置,反向遍歷集合是否含有元素。
- previous() 迭代器當(dāng)前位置,反向遍歷集合,下一個元素。
- previousIndex() 迭代器當(dāng)前位置,反向遍歷集合,返回下一個元素的下標(biāo)。
- nextIndex() 迭代器當(dāng)前位置,返回下一個元素的下標(biāo)。
使用范圍不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子類。
- ListIterator 有 add 方法,可以向 List 中添加對象;Iterator 不能。
- ListIterator 有 hasPrevious() 和 previous() 方法,可以實(shí)現(xiàn)逆向遍歷;Iterator不可以。
- ListIterator 有 nextIndex() 和previousIndex() 方法,可定位當(dāng)前索引的位置;Iterator不可以。
- ListIterator 有 set()方法,可以實(shí)現(xiàn)對 List 的修改;Iterator 僅能遍歷,不能修改。
42、快速失敗 (fail-fast) 和安全失敗 (fail-safe) 的區(qū)別是什么?
快速失?。╢ail-fast)策略的核心在于一旦發(fā)現(xiàn)數(shù)據(jù)結(jié)構(gòu)在迭代過程中被修改,系統(tǒng)會立即停止迭代并通過拋出ConcurrentModificationException異常來報告錯誤。這樣做的目的是盡早地發(fā)現(xiàn)錯誤,防止錯誤的擴(kuò)散,從而保證軟件的穩(wěn)定性和可靠性。例如,在使用ArrayList或HashMap等集合類進(jìn)行迭代時,如果嘗試在迭代過程中修改集合內(nèi)容,就會觸發(fā)這種快速失敗的行為。
安全失?。╢ail-safe)策略則相對寬容,它允許在迭代過程中對數(shù)據(jù)結(jié)構(gòu)進(jìn)行修改,而不會立即拋出異常。這種策略通常通過使用額外的措施,如迭代器復(fù)制、鎖或其他同步機(jī)制,來確保即使在多線程環(huán)境下也能安全地進(jìn)行操作。這樣可以減少因并發(fā)修改導(dǎo)致的問題,提高程序的容錯性。