Java面試題總結2

weixin_34370347發表於2017-03-21

HashMap的底層原始碼:

參考:www.cnblogs.com/chenssy/p/3521565.html
(作者: chenssy 出處: http://www.cnblogs.com/chenssy/ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。)
1.定義:
HashMap實現了Map介面,繼承AbstractMap。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

2.建構函式:
HashMap提供了三個建構函式:
HashMap():構造一個具有預設初始容量(16)和預設載入因子(0.75)的空HashMap
HashMap(int initialCapacity):構造一個指定容量(initialCapacity)和預設載入因子的空HashMap
HashMap(int initialCapacity, float loadFactor):構造一個指定容量和載入因子的空HashMap
在這裡提到了兩個引數:初始容量,載入因子。這兩個引數是影響HashMap效能的重要引數,其中容量表示雜湊表中桶的數量,初始容量是建立雜湊表時的容量,載入因子是雜湊表在其容量自動增加之前可以達到多滿的一種尺度,它衡量的是一個雜湊表的空間的使用程度,負載因子越大表示雜湊表的裝填程度越高,反之愈小。對於使用連結串列法的雜湊表來說,查詢一個元素的平均時間是O(1+a),因此如果負載因子越大,對空間的利用更充分,然而後果是查詢效率的降低;如果負載因子太小,那麼雜湊表的資料將過於稀疏,對空間造成嚴重浪費。系統預設負載因子為0.75,一般情況下我們是無需修改的。
3.資料結構:
HashMap是一個“連結串列雜湊”,如下是它資料結構:

5112817-5101554de7bc164a.png
HashMap結構

HashMap建構函式原始碼:

public HashMap(int initialCapacity, float loadFactor) {
        //初始容量不能<0
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: "
                    + initialCapacity);
        //初始容量不能 > 最大容量值,HashMap的最大容量值為2^30
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //負載因子不能 < 0
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: "
                    + loadFactor);

        // 計算出大於 initialCapacity 的最小的 2 的 n 次方值。
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
        
        this.loadFactor = loadFactor;
        //設定HashMap的容量極限,當HashMap的容量達到該極限時就會進行擴容操作
        threshold = (int) (capacity * loadFactor);
        //初始化table陣列
        table = new Entry[capacity];
        init();
    }```
4.HashMap的儲存實現:

public V put(K key, V value) {
//當key為null,呼叫putForNullKey方法,儲存null與table第一個位置中,這是HashMap允許為null的原因
if (key == null)
return putForNullKey(value);
//計算key的hash值
int hash = hash(key.hashCode());
//計算key hash 值在 table 陣列中的位置
int i = indexFor(hash, table.length);
//從i出開始迭代 e,找到 key 儲存的位置
for (Entry<K, V> e = table[i]; e != null; e = e.next) {
Object k;
//判斷該條鏈上是否有hash值相同的(key相同)
//若存在相同,則直接覆蓋value,返回舊value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value; //舊值 = 新值
e.value = value;
e.recordAccess(this);
return oldValue; //返回舊值
}
}
//修改次數增加1
modCount++;
//將key、value新增至i位置處
addEntry(hash, key, value, i);
return null;
}```
通過原始碼我們可以清晰看到HashMap儲存資料的過程為:首先判斷key是否為null,若為null,則直接呼叫putForNullKey方法。若不為空則先計算key的hash值,然後根據hash值搜尋在table陣列中的索引位置,如果table陣列在該位置處有元素,則通過比較是否存在相同的key,若存在則覆蓋原來key的value,否則將該元素儲存在鏈頭(最先儲存的元素放在鏈尾)。若table在該處沒有元素,則直接儲存。
HashMap的size一定是2^n,這樣可以保證hash值發生碰撞的概率比較小,這樣就會使得資料在table陣列中分佈較均勻,查詢速度也較快。
5.HashMap的讀取實現:

public V get(Object key) {
        // 若為null,呼叫getForNullKey方法返回相對應的value
        if (key == null)
            return getForNullKey();
        // 根據該 key 的 hashCode 值計算它的 hash 碼  
        int hash = hash(key.hashCode());
        // 取出 table 陣列中指定索引處的值
        for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
            Object k;
            //若搜尋的key與查詢的key相同,則返回相對應的value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }```
##HashMap和ConcurrentHashMap的區別:##
HashMap執行緒不安全,concurrentHashMap執行緒安全。
在ConcurrentHashMap中,把Map分成了N個Segment,put和get的時候,都是現根據key.hashCode()算出放到哪個Segment中。通過把整個Map分為N個Segment(類似HashTable),可以提供相同的執行緒安全,但是效率提升N倍,預設提升16倍。
[winwill2012部落格]http://qifuguang.me/2015/09/10/[Java併發包學習八]深度剖析ConcurrentHashMap/

##TreeMap、HashMap、LinkedHashMap的區別:##
HashMap的輸出是無序的,LinkedHashMap的輸出與輸入順序相同,TreeMap的實現是紅黑樹演算法的實現。

相關文章