面試必問之HashMap

問題1 hashmap原理?

問題1.1 hashmap底層數據結構是什麼

哈希表結構(鏈表散列:數組+鏈表)實現,結合數組和鏈表的優點。當鏈表長度超過 8 時,鏈表轉換爲紅黑樹。
在這裏插入圖片描述

問題1.2 jdk1.8爲啥要將鏈表轉爲紅黑樹呢?

鏈表的用的是線性檢索,時間複雜度是O(n),而紅黑樹的檢索方式是二分查找,平均時間複雜度是O(logn),當達到一定閾值後,二分查找是由於先行檢索的

問題1.3 什麼情況下會將鏈表轉爲紅黑樹

當來鏈表長度達到8時會轉爲紅黑樹,當桶中鏈表元素個數小於等於6時,樹結構還原成鏈表。因爲紅黑樹的平均查找長度是log(n),長度爲8的時候,平均查找長度爲3,如果繼續使用鏈表,平均查找長度爲8/2=4,這纔有轉換爲樹的必要。鏈表長度如果是小於等於6,6/2=3,雖然速度也很快的,但是轉化爲樹結構和生成樹的時間並不會太短。

還有選擇6和8,中間有個差值7可以有效防止鏈表和樹頻繁轉換。假設一下,如果設計成鏈表個數超過8則鏈表轉換成樹結構,鏈表個數小於8則樹結構轉換成鏈表,如果一個HashMap不停的插入、刪除元素,鏈表個數在8左右徘徊,就會頻繁的發生樹轉鏈表、鏈表轉樹,效率會很低。

問題1.4 能說一下什麼是紅黑樹嗎?

紅黑樹是一種特定類型的二叉樹,它是在計算機科學中用來組織數據比如數字的塊的一種結構。若一棵二叉查找樹是紅黑樹,則它的任一子樹必爲紅黑樹.
紅黑樹是一種平衡二叉查找樹的變體,它的左右子樹高差有可能大於 1,所以紅黑樹不是嚴格意義上的平衡二叉樹(AVL),但 對之進行平衡的代價較低, 其平均統計性能要強於 AVL 。
紅黑樹有5個原則:

  • 每個節點是紅色或者黑色的
  • 根節點必須是黑色的
  • 每個葉子節點都是黑色的空節點(NIL節點),即葉子節點不存儲數據
  • 紅色節點的兩個子節點必須都是黑色的(即路徑中不能存在兩個連續的紅色節點)
  • 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。

定義了兩個屬性:節點的 黑深 以及 樹的 黑高。
根據維基百科的定義,
節點的黑深指 從根節點到該節點的任意路徑中黑色節點的數量。
樹的黑高指 從根節點到葉子節點的任意路徑上的黑色節點數量。
紅黑樹通過3種操作來維持自身的平衡(插入或刪除節點後) —變色,左旋,右旋

問題1.5 還有其他集合的數據結構是紅黑樹嗎?

treemap、hashset

問題1.6 紅黑樹能替換爲二叉查找樹嗎?

不能,因爲在特定條件下二叉樹可能會退化爲線性結構

問題2 hashmap在什麼條件下擴容

  • HashMap在什麼條件下擴容?
  • 爲什麼擴容是2的n次冪?
  • 爲什麼要先高16位異或低16位再取模運算?

問題2.1 HashMap在什麼條件下擴容?

如果bucket滿了(超過load factor*current capacity),就要resize。
load factor爲0.75,爲了最大程度避免哈希衝突
current capacity爲當前數組大小。

問題2.2 爲什麼擴容是2的n次冪?

HashMap爲了存取高效,要儘量較少碰撞,就是要儘量把數據分配均勻,每個鏈表長度大致相同,這個實現就在把數據存到哪個鏈表中的算法
這個算法實際就是取模,hash%length。 但是,大家都知道這種運算不如位移運算快。
因此,源碼中做了優化hash&(length-1)。 也就是說hash%length==hash&(length-1)

問題2.3 爲什麼要先高16位異或低16位再取模運算?

來看一下jdk1.8裏的hash方法源碼。1.7的比較複雜,就不看了。

static final int hash(Object key) {
     int h;
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

hashmap這麼做,是爲了降低hash衝突的機率。

問題3 講一講hashmap的get/put的過程

hashmap put過程:
對key的hashCode()做hash運算,計算index; 如果沒碰撞直接放到bucket裏; 如果碰撞了,以鏈表的形式存在buckets後; 如果碰撞導致鏈表過長(大於等於TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹(JDK1.8中的改動); 如果節點已經存在就替換old value(保證key的唯一性) 如果bucket滿了(超過load factor*current capacity),就要resize。

hashmap get過程:
對key的hashCode()做hash運算,計算index; 如果在bucket裏的第一個節點裏直接命中,則直接返回; 如果有衝突,則通過key.equals(k)去查找對應的Entry;
• 若爲樹,則在樹中通過key.equals(k)查找,O(logn);
• 若爲鏈表,則在鏈表中通過key.equals(k)查找,O(n)。

問題4 HashMap併發問題

問題4.1 HashMap 和 HashTable 有什麼區別?

①、HashMap 是線程不安全的,HashTable 是線程安全的;
②、由於線程安全,所以 HashTable 的效率比不上 HashMap;
③、HashMap最多隻允許一條記錄的鍵爲null,允許多條記錄的值爲null,而 HashTable 不允許;
④、HashMap 默認初始化數組的大小爲16,HashTable 爲 11,前者擴容時,擴大兩倍,後者擴大兩倍+1;
⑤、HashMap 需要重新計算 hash 值,而 HashTable 直接使用對象的 hashCode

問題4.2 HashMap在併發過程中可能遇到什麼問題

  • 多線程put的時候可能導致元素丟失
  • put非null元素後get出來的卻是null
  • 多線程put後可能導致get死循環

問題4.3 怎麼得到線程安全的HashMap

一般情況下可以使用下面三種集合來替換hashMap,性能最好是ConcurrentHashMap

  • HashTable (直接new)
  • SynchronizedMap (Collections.synchronizedMap(new HashMap))
  • ConcurrentHashMap (直接new)

以上的三個都能在併發情況下正常工作,性能不同主要是體現在鎖粒度方面
這三個都使用了synchrnized 關鍵字。
其中HashTable、SynchronizedMap 是直接鎖住當前對象,不管是get、put都需要排隊。
而ConcurrentHashMap
JDK 1.7 中使用分段鎖(ReentrantLock + Segment + HashEntry),相當於把一個 HashMap 分成多個段,每段分配一把鎖,這樣支持多線程訪問。鎖粒度:基於 Segment,包含多個 HashEntry。
JDK 1.8 中使用 CAS + synchronized + Node + 紅黑樹。鎖粒度:Node(首結點)(實現 Map.Entry<K,V>)。鎖粒度降低了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章