Java面試題-集合
- 1、什么是集合?
- 2、集合和數組的區別是什么?
- 3、集合有哪些特點?
- 4、常用的集合類有哪些?
- 5、List, Set, Map三者的區別?
- 6、說說集合框架底層數據結構?
- 7、線程安全的集合類有哪些?
- 8、說說集合的快速失敗機制"fail-fast"?
- 9、怎么確保一個集合不能被修改?
- 10、說說你對Collection接口的了解?
- 11、Iterator迭代器是什么?
- 12、Iterator怎么使用?有什么特點?
- 13、如何邊遍歷邊移除Collection中的元素?
- 14、遍歷一個List有哪些不同的方式?每種方法的實現原理是什么?
- 15、List遍歷的最佳實踐是什么?
- 16、說一下 ArrayList 的優缺點?
- 17、如何實現數組和List之間的轉換?
- 18、ArrayList 和 LinkedList 的區別是什么?
- 19、ArrayList 和 Vector的區別是什么?
- 20、多線程場景下如何使用ArrayList?
- 21、為什么 ArrayList 的 elementData要加上transient修飾?
- 22、List 和 Set 的區別有哪些?
- 23、說一下HashSet的實現原理?
- 24、HashSet如何檢查重復?HashSet是如何保證數據不可重復的?
- 25、HashSet與HashMap有什么區別?
- 26、什么是Hash算法?
- 27、鏈表是什么?
- 28、說說鏈表的分類和優缺點?
- 29、說一下HashMap的實現原理?
- 30、HashMap在JDK1.7和JDK1.8中有哪些不同?
- 31、什么是紅黑樹?
- 32、HashMap的put方法的具體流程是怎樣的?
- 33、HashMap的擴容操作是怎么實現的?
- 34、HashMap是怎么解決哈希沖突的?
- 35、什么是哈希?
- 36、什么是哈希沖突?
- 37、說說HashMap的數據結構?
- 38、能否使用任何類作為Map的key?
- 39、為什么HashMap中String、Integer這樣的包裝類適合作為Key?
- 40、如果要使用Object作為HashMap的Key,應該怎么辦?
- 41、HashMap為什么不直接使用hashCode()處理后的哈希值直接作為table的下標?
- 42、HashMap的長度為什么是2的冪次方?
- 43、HashMap與HashTable 有什么區別?
- 44、什么是TreeMap?
- 45、如何決定使用 HashMap 還是TreeMap?
- 46、HashMap 和 ConcurrentHashMap 的區別?
- 47、ConcurrentHashMap 和 Hashtable的區別?
- 48、ConcurrentHashMap 底層具體實現原理是什么?
- 49、Array 和 ArrayList 有何區別?
- 50、comparable 和 comparator的區別?
- 51、Collection 和 Collections有什么區別?
- 52、TreeMap 和 TreeSet 在排序時如何比較元素?
- 53、Collections 工具類中的sort()方法如何比較元素?
1、什么是集合?
集合(Collection)是Java中一種用于存儲對象的容器,它可以包含多個對象,這些對象可以是相同類型或不同類型(依賴于具體使用的集合類型)。Java集合框架提供了一系列的接口和實現(如List
、Set
、Map
等),用于存儲和操作對象群集,支持各種操作,如添加、刪除、遍歷、排序等。
2、集合和數組的區別是什么?
- 大小:數組大小固定,一旦創建無法改變;集合大小可變,可以根據需要動態增減。
- 類型限制:數組可以包含基本數據類型和對象;集合只能包含對象。
- 功能:集合提供了更多復雜的數據操作方法(如添加、刪除、插入、遍歷等),而數組操作相對簡單。
- 性能:對于固定大小和類型的數據操作,數組通常會比集合更高效。
3、集合有哪些特點?
- List:有序集合(也稱為序列),能夠精確控制每個元素的插入位置,可以包含重復元素。
- Set:不允許重復的集合,沒有索引,不能包含相同的元素。
- Map:鍵值對集合,持有鍵(唯一)到值的映射,一個鍵最多只能映射到一個值。
- Queue:隊列,按照先進先出的原則對元素進行處理。
- Deque:雙端隊列,允許我們從兩端添加或刪除元素。
4、常用的集合類有哪些?
- List接口的實現類:
ArrayList
、LinkedList
、Vector
。 - Set接口的實現類:
HashSet
、LinkedHashSet
、TreeSet
。 - Map接口的實現類:
HashMap
、LinkedHashMap
、TreeMap
、Hashtable
。 - Queue接口的實現類:
PriorityQueue
、ArrayDeque
。 - 特殊集合類:
Stack
(繼承自Vector
)、EnumSet
、EnumMap
(轉為枚舉類型設計)。
5、List, Set, Map三者的區別?
- List:有序集合,可包含重復元素,通過索引訪問元素。
- Set:無序集合,不可包含重復元素,主要用于確保元素唯一性。
- Map:鍵值對集合,存儲唯一鍵與值的映射,鍵不可重復,值可以重復。
6、說說集合框架底層數據結構?
- ArrayList:動態數組(Resizable Array)。
- LinkedList:雙向鏈表。
- HashSet:基于哈希表(實際上是HashMap的一個實例)。
- LinkedHashSet:哈希表+雙向鏈表,維護元素插入順序。
- TreeSet:紅黑樹(自平衡的二又查找樹)。
- HashMap:哈希表,存儲鍵值對。
- LinkedHashMap:哈希表+雙向鏈表,維護鍵值對的插入順序或訪問順序。
- TreeMap:紅黑樹,根據鍵的自然順序或Comparator排序。
- PriorityQueue:優先隊列,基于堆結構。
- ArrayDeque:數組或循環數組實現的雙端隊列。
7、線程安全的集合類有哪些?
- Vector
- Hashtable
- Stack
- ConcurrentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- BlockingQueue的實現類(
ArrayBlockingQueue
,LinkedBlockingQueue
等) - ConcurrentLinkedQueue
- ConcurrentLinkedDeque
8、說說集合的快速失敗機制"fail-fast"?
快速失敗(fail-fast)機制是Java集合框架的一種錯誤檢測機制,它用于在迭代器的操作過程中,如果檢測到集合被結構性修改(增加、刪除元素等),則立即拋出ConcurrentModificationException
。這樣做可以避免在迭代過程中因為集合的結構改變而產生不可預測的行為。快速失敗行為主要通過集合的modCount
(修改計數器)實現,迭代器創建時會記錄下modCount
,每次迭代時檢查該值是否發生變化。
9、怎么確保一個集合不能被修改?
可以使用Collections
類提供的unmodifiableCollection
、unmodifiableLlist
、unmodifiableSet
、 unmodifiableMap
等方法將集合包裝為不可修改的視圖。嘗試修改這些不可修改的視圖將拋出UnsupportedOperationException
。例如:
List<String> list = new ArrayList<>();
List<String> unmodifiableList = Collections.unmodifiableList(list);
通過這種方式,原始集合list
的任何修改都不會反映在unmodifiableList
上,且unmodifiableList
不允許任何修改操作。
10、說說你對Collection接口的了解?
Collection
接口是Java集合框架的根接口,不繼承于其他接口。它提供了對集合對象進行基本操作的通用接口方法,如添加、刪除、清空集合,以及檢查集合的大小、判斷集合是否為空或是否包含某個對象等。Collection
接口是List
、Set
和Queue
接口的父接口,因此這些接口繼承并擴展了Collection
接口的功能,以支持更具體的集合操作。Collection
接口本身并不提供直接的實現,它的實現是通過其子接口(如List
、Set
等)和類(如 ArrayList
、 HashSet
等)提供的。
11、Iterator迭代器是什么?
Iterator
是一個接口,提供了遍歷集合(如List
、 Set
)的標準方法,包括檢查序列中是否還有元素(hasNext()
)、獲取序列中的下一個元素(next()
)和從集合中刪除上一次通過next()
方法返回的元素(remove()
)。Iterator
使得客戶端代碼可以統一地遍歷集合,而不需要了解其底層的結構。
12、Iterator怎么使用?有什么特點?
使用Iterator
的基本步驟如下:
- 通過調用集合的
iterator()
方法獲取迭代器實例。 - 使用
hasNext()
方法檢查集合中是否還有元素。 - 使用
next()
方法訪問集合中的下一個元素。 - 如需從集合中刪除元素,可調用
remove()
方法。
特點包括:
- 通用性:為不同類型的集合提供了統一的遍歷方式。
- 安全刪除:通過迭代器的
remove()
方法可以安全刪除集合中的元素,避免ConcurrentModificationException
。 - 單向遍歷:迭代器僅支持向前遍歷集合,不支持反向遍歷。
- 無法訪問索引:迭代器不提供獲取當前元素索引的方法。
List<String> list = new ArrayList<>();
Iterator<String> it = list.iterator();
while(it.hasNext()){String obj = it.next();System.out.println(obj);
}
13、如何邊遍歷邊移除Collection中的元素?
邊遍歷邊移除Collection
中的元素,應使用Iterator
的remove()
方法。步驟如下:
- 獲取
Collection
的迭代器。 - 使用
hasNext()
檢查是否有更多元素。 - 使用
next()
方法獲取元素。 - 使用
remove()
方法刪除上一次通過next()
方法訪問的元素。
示例代碼:
Iterator<Element> iterator = collection.iterator();
while (iterator.hasNext()) {Element element = iterator.next();//根據條件判斷是否需要刪除當前元素if (/*條件判斷*/) {iterator.remove();// 刪除元素}
}
這種方式安全,不會引發ConcurrentModificationException
。
14、遍歷一個List有哪些不同的方式?每種方法的實現原理是什么?
- for循環:通過索引直接訪問列表中的元素。實現原理是基于數組(如
ArrayList
)或鏈表(如LinkedList
)的結構,直接通過位置索引獲取元素。
for (int i = 0; i < list.size(); i++) {Element element = list.get(i);
}
- 增強for循環(for-each循環):簡化的遍歷方式,內部使用
Iterator
實現。適用于所有實現了Iterable
接口的集合類。
for (Element element : list){//使用element
}
- lterator遍歷:使用
Iterator
接口提供的hasNext()
和next()
方法遍歷元素。這是一種更通用的遍歷方式,允許在遍歷過程中安全地移除元素。
Iterator<Element> iterator - list.iterator();
2while (iterator.hasNext()){
Element element - iterator.next();
4}
- Listlterator遍歷:僅適用于
List
接口。ListIterator
提供了向前和向后遍歷列表的能力,同時支持添加、替換和刪除操作。
ListIterator<Element> listIterator = list.listIterator();
while (listIterator.hasNext()) {Element element = listIterator.next();
}
- Java 8 Stream API:提供了一種更聲明式的處理集合的方法,支持順序和并行遍歷,以及提供了豐富的操作如篩選、映射、排序等。
list.stream().forEach(element -> {//使用element
});
- for循環和增強for循環依賴于集合的內部結構進行直接訪問或隱式使用
Iterator
。 - Iterator遍歷和Listlterator遍歷提供了顯式的迭代器對象,允許在遍歷時修改集合。
- Stream APl使用函數式編程方式,可以簡化代碼并利用多核架構。
15、List遍歷的最佳實踐是什么?
Java中List
遍歷的最佳實踐取決于具體的使用場景和需求:
- 讀取操作:如果僅進行遍歷讀取,推薦使用增強for循環(for-each循環),因為它簡潔易讀。
- 性能敏感:對于
ArrayList
,直接使用基于索引的for循環可能更高效,尤其是在大列表中;對于LinkedList
,使用增強for循環或Iterator
遍歷效率更高。 - 需要修改列表:如果在遍歷過程中需要修改列表(添加、刪除、替換元素),使用
Iterator
或ListIterator
,它們提供了安全修改集合的方法。 - Java 8及以上:考慮使用Stream API,它提供了更高級的操作(如過濾、轉換等),并可以利用并行處理來提高性能。
16、說一下 ArrayList 的優缺點?
優點:
- 隨機訪問效率高:基于動態數組實現,支持快速隨機訪問,通過索引直接訪問元素的時間復雜度為O(1)。
- 動態擴容:能根據需要自動增加存儲容量,使用方便。
- API豐富:提供了豐富的方法用于操作列表中的元素,包括添加、刪除、查找、遍歷等。
缺點:
- 插入和刪除效率低:在列表的中間或開始位置添加或刪除元素需要移動后續所有元素,時間復雜度為O(n)。
- 內存開銷:在動態擴容時,需要重新分配數組并復制舊數組的內容到新數組,可能會有額外的內存開銷。
- 容量浪費:自動擴容機制可能會導致實際分配的容量大于實際所需,造成空間浪費。
17、如何實現數組和List之間的轉換?
數組轉List:
使用Arrays.asList(T...a)
方法將數組轉換為固定大小的列表。
String[] array = {"a","b","c"};
List<String> list = Arrays.asList(array);
List轉數組:
使用List
的toArray(T[] a)
方法將列表轉換為數組。
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
String[] array = list.toArray(new String[0]);
注意:Arrays.aslList
返回的列表是固定大小的,對其進行添加或刪除操作會拋出UnsupportedOperationException
。若要創建一個可變的列表,可以使用新的ArrayList<>(Arrays.asList(array))
。
18、ArrayList 和 LinkedList 的區別是什么?
- 內部結構:
ArrayList
基于動態數組實現,支持快速隨機訪問;LinkedList
基于雙向鏈表實現,每個元素都有指向前后元素的引用。 - 性能:
ArrayList
的隨機訪問速度快(O(1)),但在列表中間或開頭插入和刪除元素的速度較慢(O(n));LinkedList
的插入和刪除操作速度快(O(1)),但隨機訪問速度慢(O(n))。 - 內存占用:
ArrayList
因為大小可變,可能會有額外的內存占用;LinkedList
對于每個元素都需要額外的內存空間來存儲前后元素的引用。 - 使用場景:
ArrayList
適合頻繁的查找操作,LinkedList
適合頻繁的插入和刪除操作。
19、ArrayList 和 Vector的區別是什么?
- 同步性:
Vector
是同步的,適用于多線程環境,但因為同步帶來額外開銷,性能較低;ArrayList
是非同步的,適用于單線程環境,性能較高。 - 數據增長:
ArrayList
的數據增長率默認為50%,而Vector
默認增長率是100%,也可以在創建時指定增長率。 - 遺留性:
Vector
是Java早期版本的一部分,被視為遺留類,ArrayList
是Java集合框架(JCF)的一部分,是較新的、更輕量級的選擇。
20、多線程場景下如何使用ArrayList?
在多線程場景下使用ArrayList
時,可以采取以下措施以確保線程安全:
- 使用
**Collections.synchronizedList**
方法:將ArrayList
包裝為同步的列表。
List<String> syncList = Collections.synchronizedList(new ArrayList<String>());
- 使用
**CopyOnWriteArrayList**
:適用于讀多寫少的并發場景,因為每次修改(添加、刪除、設置等)都會復制整個基礎數組。
List<String> cowList = new CopyOnWriteArrayList<>();
- 手動同步:在對
ArrayList
進行操作的代碼塊前后使用synchronized
關鍵字手動同步。
synchronized(list) {// list操作
}
21、為什么 ArrayList 的 elementData要加上transient修飾?
ArrayList
的elementData
數組被聲明為transient
是為了在序列化過程中控制哪些數據被序列化,避免將數組中的空元素也序列化。這樣做可以最小化序列化存儲空間的使用,只序列化數組中實際存儲的元素,而不是整個底層數組。在反序列化時,只根據存儲的元素重新構建數組,從而提高了效率和節省了存儲空間。
22、List 和 Set 的區別有哪些?
- 元素唯一性:
List
允許重復的元素,按照插入順序排序;Set
不允許重復的元素,通常不保證元素的順序(除LinkedHashSet
)。 - 索引訪問:
List
支持通過索引位置訪問元素;Set
不支持索引訪問。 - 接口實現:
List
和Set
是Java集合框架中的兩個不同的接口,分別有各自的實現類,如ArrayList
、LinkedList
為List
接口的實現,而HashSet
、LinkedHashSet
、TreeSet
為Set
接口的實現。
23、說一下HashSet的實現原理?
HashSet
是基于HashMap
實現的。它使用HashMap
的鍵來存儲元素,每個元素都映射到一個固定的虛擬值(HashMap
的值部分)。HashSet
利用HashMap
鍵的唯一性來確保其元素的唯一性,并且通過哈希表實現,因此具有很高的查找和插入效率。當向HashSet
添加元素時,實際上是將元素作為HashMap
的鍵添加,而對應的值則是一個預定義的靜態的新對象(因為HashMap
是鍵值對映射,而HashSet
只關心鍵)。
24、HashSet如何檢查重復?HashSet是如何保證數據不可重復的?
HashSet
檢查重復并保證數據不可重復的機制基于其底層的 HashMap
實現。當向HashSet
添加元素時,實際上是將該元素作為HashMap
的鍵:
- 哈希值計算:首先,利用元素的
hashCode()
方法計算其哈希值,這個哈希值用于定位在哈希表中的存儲位置。 - 鍵的唯一性:
HashMap
保證了每個鍵的唯一性。如果插入的元素(作為HashMap
的鍵)的哈希值與已存在的某個元素的哈希值相同,HashMap
會進一步調用equals()
方法來檢查這兩個元素是否真的相等。- 如果
equals()
返回true
,則認為這兩個元素相等,新的插入操作不會發生,從而保證了HashSet
中元素的唯一性。 - 如果
equals()
返回false
,則認為這兩個元素雖然哈希值相同但不等(發生了哈希沖突),HashMap
將通過某種沖突解決機制(如鏈表或紅黑樹)來存儲這兩個元素,保證它們都被保存在結構中。
- 如果
通過這種方式,HashSet
利用HashMap
的鍵的唯一性來保證自身存儲的元素的唯一性。
25、HashSet與HashMap有什么區別?
- 用途差異:
HashSet
是一個不允許重復元素的集合,用于存儲唯一的值;HashMap
是一個鍵值對集合,存儲鍵值映射,允許通過鍵快速訪問數據。 - 數據結構:
HashSet
內部實際上是通過HashMap
實現的,它存儲元素作為HashMap
的鍵,所有的值都映射到一個預定義的常量上;HashMap
存儲鍵值對,每個鍵映射到一個具體的值。 - 方法和功能:
HashSet
主要提供添加、刪除、包含等操作,關注點在于元素的唯一性;而HashMap
提供了更豐富的操作,如獲取和設置鍵值對,檢查鍵或值是否存在等。 - 性能:在使用上,兩者的性能考量主要依賴于
hashCode()
方法的實現,以及沖突解決策略,但HashSet
主要關注集合操作,而HashMap
關注鍵值對的映射和訪問。
26、什么是Hash算法?
Hash算法是一種將任意長度的輸入(或稱為消息)通過散列算法處理,轉換成固定長度輸出的過程。該輸出稱為散列值或哈希值。Hash算法的特點包括:對于任何給定的輸入,輸出都是確定的;不同的輸入盡可能產生不同的輸出(盡管會有哈希碰撞的可能性);算法執行過程是單向的,即從哈希值不能逆向推導出原始數據。Hash算法廣泛應用于數據檢索、加密、數據完整性驗證等領域。
27、鏈表是什么?
鏈表是一種數據結構,由一系列節點組成,每個節點包含數據部分和一個或多個指向其他節點的引用(指針)。鏈表允許高效的元素插入和刪除操作,因為這些操作只需要改變相鄰節點的引用。
28、說說鏈表的分類和優缺點?
鏈表主要分為三種類型:
- 單向鏈表:每個節點包含數據和指向下一個節點的指針。可以高效地進行插入和刪除操作,但只能向一個方向遍歷。
- 雙向鏈表:每個節點包含數據、指向前一個節點的指針和指向下一個節點的指針。可以向兩個方向遍歷,插入和刪除操作相對更靈活和高效。
- 循環鏈表:單向或雙向鏈表的變種,其中最后一個節點的下一個指針指向第一個節點,形成一個環。便于從任何節點開始遍歷整個鏈表。
優點:
- 動態數據結構:鏈表可以根據需要動態地分配內存,不需要在創建時確定大小。
- 插入和刪除高效:相較于數組,鏈表在插入和刪除節點時不需要移動其他元素,只需修改指針即可。
缺點:
- 訪問時間:相較于數組,鏈表的訪問時間較長,不能直接通過索引訪問元素,需要從頭開始遍歷。
- 內存占用:由于每個元素需要額外存儲指針,所以相較于數組鏈表的內存占用更大。
- 逆向遍歷困難:單向鏈表的逆向遍歷非常困難,需要額外的數據結構或算法。
29、說一下HashMap的實現原理?
HashMap
是基于哈希表實現的。核心原理如下:
- 數據存儲:
HashMap
存儲鍵值對,其中鍵是唯一的。 - 哈希函數:使用鍵的
hashCode()
方法計算哈希碼,然后通過散列函數將此哈希碼映射到哈希表中的一個位置(桶)以決定鍵值對的存儲位置。 - 處理哈希沖突:當不同的鍵產生相同的哈希碼(哈希沖突)時,
HashMap
采用鏈表(JDK 1.8之前)或紅黑樹(JDK 1.8及以后,鏈表長度超過一定閾值時轉換為紅黑樹)來存儲相同哈希碼的不同鍵值對。 - 動態擴容:當哈希表中的數據量超過負載因子(load factor)和當前容量的乘積時,哈希表將進行擴容(通常是翻倍),然后重新計算已有的鍵值對在新哈希表中的位置。
- 鍵值訪問:通過鍵的哈希碼快速定位到其值的存儲位置,實現高效的數據查找、插入和刪除操作。
30、HashMap在JDK1.7和JDK1.8中有哪些不同?
主要區別在于內部數據結構和擴容機制的改進:
- 內部數據結構:
- JDK 1.7:使用數組+鏈表的結構。當多個鍵的哈希值相同(哈希沖突)時,會在相同哈希值的桶位置形成一個鏈表。
- JDK 1.8:引入了數組+鏈表+紅黑樹的結構。當鏈表長度超過閾值(默認為8)時,鏈表轉換為紅黑樹,以減少搜索時間。
- 擴容機制:
- JDK 1.7:在擴容時,新的數組大小是原來的兩倍,所有元素需要重新計算哈希值并分配到新的桶中,這個過程效率較低。
- JDK 1.8:優化了擴容算法,通過保留原有節點的順序,減少了重新計算哈希值的需要,只需判斷節點存儲在原位置還是移動到新的位置,提高了擴容的效率。
這些改進提升了HashMap
在處理大量數據時的性能,尤其是減少了搜索時間和擴容成本。
31、什么是紅黑樹?
紅黑樹是一種自平衡的二又搜索樹,它通過每個節點增加一個存儲位表示節點的顏色(紅或黑),并通過一系列的規則和旋轉操作來保持樹的平衡。這些規則包括:
- 每個節點要么是紅的,要么是黑的。
- 根節點是黑的。
- 所有葉子節點(NIL節點,空節點)都是黑的。
- 每個紅節點的兩個子節點都是黑節點(從每個葉子到根的所有路徑上不能有兩個連續的紅節點)。
- 從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點。
紅黑樹的這些性質確保了最長的路徑不會超過最短的路徑的兩倍,因而接近于平衡。這保證了紅黑樹在插入、刪除和查找操作中都能保持較高的性能,最壞情況下的時間復雜度為O(log n)。
32、HashMap的put方法的具體流程是怎樣的?
HashMap
的put
方法流程(基于JDK 1.8)如下:
- 計算鍵的哈希值:首先,使用鍵對象的
hashCode()
方法計算哈希值,然后通過哈希函數處理這個哈希值以減少碰撞。 - 定位桶位置:使用處理后的哈希值與數組長度減一進行位運算(假設數組長度是2的幕),確定鍵值對應該存放的桶(數組索引)位置。
- 檢查鍵是否存在:
- 如果目標桶為空,直接在該位置創建一個節點存儲鍵值對;
- 如果目標桶不為空(即發生了哈希碰撞)則遍歷該桶(可能是鏈表或紅黑樹):
- 如果鍵已存在,更新其對應的值;
- 如果鍵不存在,則在適當的位置添加一個新節點(鏈表末尾或紅黑樹中)。
- 樹化檢查:如果鏈表長度超過閾值(默認為8),并且數組長度大于或等于64,那么將鏈表轉換為紅黑樹,以提高后續的查找和插入效率。
- 檢查是否需要擴容:如果當前
HashMap
的大小超過了容量與負載因子(默認為0.75)的乘積,那么進行擴容(容量翻倍)并重新分配所有的鍵值對。 - 返回值:如果插入的鍵已經存在,則返回舊值;如果是新插入的鍵,則返回
null
。
33、HashMap的擴容操作是怎么實現的?
HashMap
的擴容操作主要包括以下步驟:
- 創建新數組:創建一個新的數組,大小通常是原數組大小的兩倍。
- 重新計算索引:遍歷原數組中的所有元素,對每個元素重新計算其在新數組中的位置。這是通過使用元素的哈希值與新數組的長度進行位運算來完成的。
- 重新分配元素:根據重新計算得到的索引,將所有元素重新放置到新數組的相應位置上。如果原位置是鏈表或紅黑樹,則需要遍歷這些結構,并對每個節點進行同樣的重新索引操作。
- 更新引用:最后,將
HashMap
的內部引用從舊數組更新為指向新數組。
34、HashMap是怎么解決哈希沖突的?
HashMap
解決哈希沖突的主要方式是通過鏈地址法,即在發生沖突的桶位置使用鏈表(JDK 1.8之前)或紅黑樹(JDK 1.8及以后,在鏈表長度超過一定閾值時,鏈表轉換為紅黑樹)來存儲多個元素。這樣,即使多個鍵映射到同一個桶(索引位置),它們也可以通過鏈表或紅黑樹結構被有效地存儲和檢索。在查找、插入或刪除操作時,HashMap
會先定位到桶的位置,然后遍歷鏈表或紅黑樹來進行具體操作。這種方法允許多個鍵共享同一個桶位置,同時保持操作的效率。
35、什么是哈希?
哈希是一種將任意長度的輸入通過哈希函數轉換成固定長度輸出的過程,輸出結果稱為哈希值。哈希過程的主要特點是高效性和確定性,即相同的輸入總是產生相同的輸出,不同的輸入盡量產生不同的輸出。哈希廣泛應用于數據檢索、數據加密、數據完整性校驗等領域。
36、什么是哈希沖突?
哈希沖突是指兩個或多個不同的輸入值在經過哈希函數處理后得到了相同的哈希值的情況。由于哈希函數的輸出空間有限,而輸入空間可能是無限的,因此不同的輸入有可能映射到同一個輸出,即發生哈希沖突。處理哈希沖突是哈希表等數據結構設計中的一個重要考慮因素。
37、說說HashMap的數據結構?
HashMap
的數據結構是一個結合了數組和鏈表(或紅黑樹)的復合結構。它使用一個數組來存儲數據,數組的每個槽位(桶)可以指向一個鏈表或紅黑樹的結構,用于存儲具有相同哈希值(經過哈希函數處理后的索引)的多個鍵值對。在JDK 1.8及以后版本中,當鏈表長度超過一定閾值時,鏈表會被轉換為紅黑樹,以提高搜索效率。
38、能否使用任何類作為Map的key?
是的,理論上可以使用任何類作為Map
的key
。但是,為了確保Map正常工作,該類應正確重寫hashCode()
和 equals()
方法。這是因為Map
(特別是HashMap
)依賴這兩個方法來確定鍵的唯一性并維護鍵值對。如果這兩個方法沒有被適當地重寫,可能導致Map
表現出不正確的行為,如丟失數據或無法檢索到數據。
39、為什么HashMap中String、Integer這樣的包裝類適合作為Key?
String
、Integer
等包裝類適合作為HashMap
中的Key
,因為它們已經正確地重寫了hashCode()
和 equals()
方法,確保了相同的實例會有相同的哈希碼,且只有當兩個對象邏輯上等價時,equals()
方法才返回 true
。這使得這些類型的對象能夠保持鍵的唯一性和一致性,從而在HashMap
中高效地存儲和檢索數據。
40、如果要使用Object作為HashMap的Key,應該怎么辦?
- 重寫
**hashCode()**
方法:確保邏輯上相等的對象返回相同的哈希碼。 - 重寫
**equals()**
方法:確保正確比較兩個鍵對象的邏輯等價性,即當且僅當兩個對象邏輯上相等時,equals()
方法返回true
。
這樣做是為了保證HashMap
能夠正確地處理鍵的唯一性和檢索操作。未正確重寫這些方法可能導致HashMap
行為不正確,比如查找失敗或意外的數據覆蓋。
41、HashMap為什么不直接使用hashCode()處理后的哈希值直接作為table的下標?
使用hashCode()
處理后的哈希值直接作為數組下標的話,會面臨幾個問題:
- 數組大小限制:直接使用哈希值作為下標可能導致非常大的數組索引,遠遠超過
HashMap
的實際數組大小,這是不切實際的。 - 負數問題:
hashCode()
方法返回的是int
類型,可能是負數,而數組索引不能是負數。
為了解決這些問題,HashMap
通過對哈希值進行額外的哈希函數處理,并與數組長度-1進行位運算(通常是與操作),來確保計算出來的索引在數組的有效范圍內,同時也幫助分散哈希值,減少哈希沖突。
42、HashMap的長度為什么是2的冪次方?
主要有兩個原因:
- 位運算效率:使用2的冪次作為數組長度,允許
HashMap
通過位運算(hashCode & (length-1)
)代替模運算來計算索引,大大提高了計算索引的效率。 - 均勻分布:2的冪次方作為長度可以幫助哈希值更均勻地分布在整個數組中,減少哈希沖突,從而提高
HashMap
的性能。
43、HashMap與HashTable 有什么區別?
- 線程安全:
HashTable
是線程安全的,所有方法都是同步的;而HashMap
是非線程安全的。 - 性能:因為
HashTable
的方法是同步的,所以在單線程環境下比HashMap
性能低。 - 空值:
HashMap
允許鍵和值為null
;HashTable
不允許鍵或值為null
。 - 迭代器:
HashMap
的迭代器是快速失敗的(fail-fast),而HashTable
的枚舉器不是。
44、什么是TreeMap?
TreeMap
是基于紅黑樹(一種自平衡二叉查找樹)的Map
接口實現。它能夠保持鍵的自然排序(根據其compareTo()
方法)或根據構造時提供的Comparator
進行排序。TreeMap
實現了SortedMap
接口,因此它支持有序集合操作,如返回指定范圍的子映射、獲取第一個(最低)和最后一個(最高)鍵等。由于其基于紅黑樹,TreeMap
的插入、刪除、查找等操作的時間復雜度為O(log n)。
45、如何決定使用 HashMap 還是TreeMap?
- 性能需求:如果關注插入、刪除、查找的性能,并且不需要排序,
HashMap
(平均O(1)的時間復雜度)通常是更好的選擇。如果需要一個按照自然順序或自定義順序來遍歷鍵,TreeMap
(保證了O(log n)的時間復雜度)可能更適合。 - 排序需求:如果需要鍵值對保持有序,選擇
TreeMap
;如果不需要排序,HashMap
效率更高。 - 線程安全:如果需要線程安全,兩者都不是最佳選擇,可能需要考慮
ConcurrentHashMap
或Collections.synchronizedMap
包裝器。但就單個線程或已有外部同步措施情況而言,選擇依據是性能和排序需求。 - 內存使用:
HashMap
通常使用較少的內存,因為TreeMap
需要維護紅黑樹的結構。
46、HashMap 和 ConcurrentHashMap 的區別?
- 線程安全:
ConcurrentHashMap
是線程安全的,提供了高效的并發訪問;而HashMap
是非線程安全的,不適合在并發環境下使用而不進行外部同步。 - 內部結構:
ConcurrentHashMap
在內部使用了分段鎖(JDK1.7)或使用 CAS操作和synchronized
關鍵字來減少鎖競爭(JDK1.8),從而提高并發訪問性能;HashMap
沒有這樣的機制,其所有操作都不是同步的。 - 迭代器弱一致性:
ConcurrentHashMap
的迭代器提供弱一致性遍歷,而不是HashMap
的快速失敗遍歷方式。 - 性能:由于
ConcurrentHashMap
的并發優化,它在多線程環境下比同步的HashMap
(如通過Collections.synchronizedMap
包裝的HashMap
)提供了更好的讀寫性能。 - 功能差異:
ConcurrentHashMap
移除了一些如contains
方法,改為containsValue
和containsKey
,更明確地表達了方法的意圖。
47、ConcurrentHashMap 和 Hashtable的區別?
- 線程安全實現:
ConcurrentHashMap
使用分段鎖(JDK 1.7) 和CAS操作(JDK 1.8及以后)來提高并發訪問性能,只鎖定部分數據結構;而Hashtable
使用方法級的同步,每個方法都是同步的,鎖定整個數據結構,導致并發性能較低。 - 性能:因為
ConcurrentHashMap
的并發優化,它在多線程環境下提供比Hashtable
更高的讀寫性能。 - 迭代器:
ConcurrentHashMap
的迭代器提供弱一致性視圖,不會拋出ConcurrentModificationException
;而Hashtable
的枚舉器和迭代器在結構被并發修改時可能拋出此異常。 - 空鍵和空值:
ConcurrentHashMap
不允許鍵(keys)或值(values)為null
;而Hashtable
允許非空鍵和值。 - 設計目的:
ConcurrentHashMap
專為并發使用場景設計,提供了一些原子操作方法;Hashtable
是早期Java版本的遺留類,主要用于單線程或通過外部同步機制在多線程中使用。
48、ConcurrentHashMap 底層具體實現原理是什么?
ConcurrentHashMap
的底層實現原理在JDK1.7和JDK1.8中有所不同:
- JDK 1.7:
ConcurrentHashMap
使用分段鎖(Segment鎖)機制。它內部維護一個Segment
數組,每個Segment
實質上是一個小的哈希表,擁有自己的鎖。對ConcurrentHashMap
的操作只會鎖定必要的Segment
,從而減少鎖競爭,提高并發效率。每個Segment
負責管理它所包含的鍵值對。 - JDK 1.8:改進了數據結構,去掉了
Segment
,直接使用節點數組加鏈表和紅黑樹的結構。通過使用synchronized
和 CAS(無鎖機制)來控制并發,同時使用了紅黑樹來優化長鏈表的查找時間。在JDK1.8中,ConcurrentHashMap
在鏈表長度超過一定閾值時將鏈表轉換為紅黑樹,提高搜索效率。
在兩個版本中,ConcurrentHashMap
都能夠通過細粒度的鎖控制或無鎖機制來實現高效的并發訪問,尤其是在讀操作遠遠多于寫操作的場景下表現良好。
49、Array 和 ArrayList 有何區別?
- 類型:
Array
是固定大小的,可以存儲基本類型或對象;ArrayList
是可變大小的,只能存儲對象。 - 大小:
Array
的大小在創建時確定,不能動態改變;ArrayList
的大小可動態增長。 - 性能:對于基本類型數據,
Array
有更好的性能,因為ArrayList
使用包裝類作為泛型存儲,有裝箱和拆箱的開銷。 - 功能:
ArrayList
提供了更多的方法和功能,如自動擴容、便捷的插入、刪除等操作。
50、comparable 和 comparator的區別?
- Comparable:是一個對象自比較其自身與另一個對象的接口。如果一個類實現了
Comparable
接口,就意味著該類支持排序。Comparable
接口有一個compareTo(Object o)
方法,用于定義默認的排序方式。 - Comparator:是一個比較器接口,可以定義兩個對象的比較方式。如果你想要控制某個類的排序邏輯,但是這個類不能修改或者你想要有多種排序方式,可以通過實現
Comparator
接口,使用它的compare(Object o1, Object o2)
方法來實現。
簡而言之,Comparable
用于實現自然排序,而Comparator
用于實現自定義排序邏輯。
51、Collection 和 Collections有什么區別?
- Collection:是一個接口,它是各種集合結構(如
list
、Set
等)的根接口,提供了對集合對象進行基本操作的通用接口方法。 - Collections:是一個包含有關集合操作的靜態方法的工具類,如排序、搜索等,用于操作或返回集合。
52、TreeMap 和 TreeSet 在排序時如何比較元素?
TreeMap
和 TreeSet
在排序元素時,可以依賴元素的自然排序或者通過提供一個自定義的Comparator
對象來定義排序規則。
- 自然排序:當沒有提供
Comparator
時,元素類必須實現Comparable
接口,并且compareTo(Object o)
方法定義了其自然排序的邏輯。 - 自定義排序:通過構造函數傳入一個
Comparator
對象,使用該比較器的compare(Object o1, Object o2)
方法來比較元素。
53、Collections 工具類中的sort()方法如何比較元素?
Collections.sort()
方法比較元素的方式取決于傳入的集合元素類型:
- 如果集合元素實現了
Comparable
接口,sort()
方法會使用這些元素的compareTo()
方法進行自然排序。 - 可以重載
sort()
方法,傳入一個自定義的Comparator
對象,這時候會使用該Comparator的compare()
方法來比較元素,實現自定義排序。