前言
本來想著給自己放鬆一下,刷刷部落格,突然被幾道面試題難倒!說說Hashtable 與 HashMap 的區別?HashMap 中的 key 我們可以使用任何類作為 key 嗎?HashMap 的長度為什麼是 2 的 N 次方呢?HashMap 與 ConcurrentHashMap 的異同?紅黑樹有哪幾個特徵?似乎有點模糊了,那就大概看一下面試題吧。好記性不如爛鍵盤
*** 12萬字的java面試題整理 ***
說說Hashtable 與 HashMap 的區別
- 都實現了 Map、Cloneable、Serializable(當前 JDK 版本 1.8)。
- Hashtable 中大部分 public 修飾普通方法都是synchronized 欄位修飾的,是執行緒安全的,HashMap 是非執行緒安全的。
- Hashtable 的 key 不能為 null,value 也不能為 null,這個可以從 Hashtable 原始碼中的 put 方法看到,判斷如果 value 為 null 就直接丟擲空指標異常,在 put 方法中計算 key 的 hash 值之前並沒有判斷 key 為 null 的情況,那說明,這時候如果 key 為空,照樣會丟擲空指標異常。
- HashMap 的 key 和 value 都可以為 null。在計算 hash 值的時候,有判斷,如果key==null ,則其 hash=0 ;至於 value 是否為 null,根本沒有判斷過。
- Hashtable 直接使用物件的 hash 值。hash 值是 JDK 根據物件的地址或者字串或者數字算出來的 int 型別的數值。然後再使用除留餘數法來獲得最終的位置。然而除法運算是非常耗費時間的,效率很低。HashMap 為了提高計算效率,將雜湊表的大小固定為了 2 的冪,這樣在取模預算時,不需要做除法,只需要做位運算。位運算比除法的效率要高很多。
- 預設情況下,初始容量不同,Hashtable 的初始長度是 11,之後每次擴充容量變為之前的2n+1(n 為上一次的長度)而 HashMap 的初始長度為 16,之後每次擴充變為原來的兩倍。
另外在 Hashtable 原始碼註釋中有這麼一句話:
Hashtable is synchronized. If a thread-safe implementation is not needed, it is
recommended to use HashMap in place of Hashtable . If a thread-safe highly-
concurrent implementation is desired, then it is recommended to useConcurrentHashMap in place of Hashtable
大致意思:Hashtable 是執行緒安全,推薦使用 HashMap 代替 Hashtable;如果需要執行緒安全高併發的話,推薦使用 ConcurrentHashMap 代替 Hashtable。
這個回答完了,面試官可能會繼續問:HashMap 是執行緒不安全的,那麼在需要執行緒安全的情況下還要考慮效能,有什麼解決方式?
這裡最好的選擇就是 ConcurrentHashMap 了,但面試官肯定會叫你繼續說一下ConcurrentHashMap 資料結構以及底層原理等。
HashMap 中的 key 我們可以使用任何類作為 key 嗎?
平時可能大家使用的最多的就是使用 String 作為 HashMap 的 key,但是現在我們想使用某個自定義類作為 HashMap 的 key,那就需要注意以下幾點:
- 如果類重寫了 equals 方法,它也應該重寫 hashCode 方法。
- 類的所有例項需要遵循與 equals 和 hashCode 相關的規則。
- 如果一個類沒有使用 equals,你不應該在 hashCode 中使用它。
- 咱們自定義 key 類的最佳實踐是使之為不可變的,這樣,hashCode 值可以被快取起來,擁有更好的效能。不可變的類也可以確保 hashCode 和 equals 在未來不會改變,這樣就會解決與可變相關的問題了。
HashMap 的長度為什麼是 2 的 N 次方呢?
為了能讓 HashMap 存資料和取資料的效率高,儘可能地減少 hash 值的碰撞,也就是說盡量把資料能均勻的分配,每個連結串列或者紅黑樹長度儘量相等。
我們首先可能會想到 % 取模的操作來實現。下面是回答的重點喲:
取餘(%)操作中如果除數是 2 的冪次,則等價於與其除數減一的與(&)操作(也就是說hash % length == hash &(length - 1) 的前提是 length 是 2 的 n 次方)。並且,採用二進位制位操作 & ,相對於 % 能夠提高運算效率。
這就是為什麼 HashMap 的長度需要 2 的 N 次方了。
HashMap 與 ConcurrentHashMap 的異同
- 都是 key-value 形式的儲存資料;
- HashMap 是執行緒不安全的,ConcurrentHashMap 是 JUC 下的執行緒安全的;
- HashMap 底層資料結構是陣列 + 連結串列(JDK 1.8 之前)。JDK 1.8 之後是陣列 + 連結串列 + 紅黑樹。當連結串列中元素個數達到 8 的時候,連結串列的查詢速度不如紅黑樹快,連結串列會轉為紅黑樹,紅黑樹查詢速度快;
- HashMap 初始陣列大小為 16(預設),當出現擴容的時候,以 0.75 * 陣列大小的方式進行擴容;
- ConcurrentHashMap 在 JDK 1.8 之前是採用分段鎖來現實的 Segment + HashEntry,Segment 陣列大小預設是 16,2 的 n 次方;JDK 1.8 之後,採用 Node + CAS + Synchronized來保證併發安全進行實現
紅黑樹有哪幾個特徵?
紅黑樹是一種自平衡二叉查詢樹,它在每個節點上增加了一個儲存位來表示節點的顏色(紅色或黑色),透過顏色的約束和一些旋轉操作來保持樹的平衡。紅黑樹具有以下特徵:
- 節點是紅色或黑色:每個節點都有一個顏色屬性,可以是紅色或黑色。
- 根節點是黑色:樹的根節點總是黑色的。
- 所有葉子節點都是黑色:這裡的“葉子”指的是空(null)節點,它們不包含資料,並且被認為是黑色的。
- 紅色節點的子節點必須是黑色:如果一個節點是紅色的,則它的兩個孩子節點都必須是黑色的。這意味著從任意節點到其子孫葉節點的所有路徑上不會有兩個連續的紅色節點。
- 從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點:這條規則保證了沒有一條路徑會比其他路徑長出兩倍以上,從而維持了大致的平衡。
這些性質確保了紅黑樹的高度最多為2log(n+1),其中n是樹中節點的數量。因此,紅黑樹上的基本操作如插入、刪除、查詢的時間複雜度都是O(log n),這使得紅黑樹成為一種高效的平衡二叉搜尋樹實現方式。
紅黑樹透過一系列的操作如左旋、右旋以及顏色變化來維護上述性質。當插入或刪除節點時,可能會破壞紅黑樹的性質,這時需要透過調整來恢復這些性質,以確保樹仍然滿足紅黑樹的所有特徵。