原創|你真的瞭解HashMap嗎?(HashMap源碼分析)

前言面試

講講hashMap,簡單的東西你還真不必定理解,真心的以爲須要有一篇文章把它給講明白,這樣就不再怕面試被問到了。數組

**HashMap介紹
HashMap初始化
HashMap擴容機制
HashMap數據結構
HashMap哈希衝突的解決
HashMap使用**安全

1、HashMap介紹數據結構

他是基於哈希表的 Map 接口的實現。此實現提供全部可選的映射操做,並容許使用 null 值和 null 鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。此實現假定哈希函數將元素適當地分佈在各桶之間,可爲基本操做(get 和 put)提供穩定的性能。另外,HashMap是非線程安全的,而Hashtable是線程安全的。

HashMap是繼承了AbstractMap類,實現了 Map,Cloneable, Serializable 接口.
public class HashMap<K,V>
    extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

2、HashMap初始化函數

重要參數說明性能

//初始化的容量大小
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    //最大容量大小
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //默認負載因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

HashMap初始化的時候就作兩件事,初始化了容量(比做一個桶)的大小爲16和負載因子爲0.75學習

public HashMap() {
     //初始化桶大小DEFAULT_INITIAL_CAPACITY=16  
     //負載因子DEFAULT_LOAD_FACTOR=0.75
     this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        //若是容量超過最大的值,取最大值
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //初始化負載因子0.75
        this.loadFactor = loadFactor;
        //初始化桶大小16
        threshold = initialCapacity;
        init();
    }

3、HashMap擴容機制this

負載因子能夠理解爲飽滿度,負載因子越大,佔用的的空間越小,可是查詢的效率越低。負載因子越小,佔用空間越大,可是會提升查詢效率。這是由數據結構決定的,下面講。spa

HashMap 的實際容量就是因子*容量,其默認值是 16×0.75=12;這個很重要,對效率有必定影響!當存入HashMap的對象超過這個容量時,HashMap 就會就須要 resize(擴容2倍後重排)。線程

private void inflateTable(int toSize) {
        //最接近且大於toSize的2的冪數
        int capacity = roundUpToPowerOf2(toSize);
        //定義最大的實際容量,超過這個值就須要擴容
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        //定義Entry數組,放put數據
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
  }
  //最接近且大於number的2的冪數,例如number爲3返回結果是4,number爲4返回結果爲4
   private static int roundUpToPowerOf2(int number) {
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }
//添加元素,並判斷是否須要擴容
void addEntry(int hash, K key, V value, int bucketIndex) {
      //size爲當前數組大小,threshold爲實際最大容量,若是大於就擴容
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);//擴容
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        //添加
        createEntry(hash, key, value, bucketIndex);
    }
    //擴容
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        //判斷老的數組是否等於最大容量,若是等於不容許擴容
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        //定義新的數據
        Entry[] newTable = new Entry[newCapacity];
        //從新再新的數組中定義老的數據
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
    //須要從新在新的數組中計算保存位置,並保存
void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

由於從新建立新的數據,從新計算元素的存儲位置很是的影響性能,因此最好預估元素的多少,在建立HashMap的時候定義它的大小,最好爲2的冪數,這樣能夠更好的利用空降。

4、HashMap數據結構

HashMap是一個散列桶(數組和鏈表),它存儲的內容是鍵值對key-value映射,數組和鏈表的數據結構,能在查詢和修改方面繼承了數組的線性查詢和鏈表的尋址修改。(橫排表示數組,縱排表示鏈表)

clipboard.png

5、HashMap數據碰撞的解決

由於哈希值有哈希衝突的存在,因此不一樣的key可能有相同的哈希值。這個是後就須要經過鏈表結構來解決問題了。

正常狀況,當咱們put存儲一個key-value數據的時候,HashMap會計根據key的哈希值計算應該在桶中保存的位置,判斷該位置是否有數據,若是有數據,須要判斷hash和key時候相等,若是相等,新的值替換老的值,並返回老的值。(這個不是「碰撞」,這中狀況key是同樣的)
public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        //計算哈希
        int hash = hash(key);
        //計算桶中的位置
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
          //判斷哈希和key是否同樣
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {        //同樣
              //哈希同樣,key也同樣
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        //i位置數據爲空;或者數據不爲空,hash不同;或者哈希同樣,key不同
        modCount++;
        //添加
        addEntry(hash, key, value, i);
        return null;
    }

i位置數據爲空;或者數據不爲空,hash不同;或者哈希同樣(哈希碰撞),key不同,走下面流程:

下面源碼中,bucketIndex爲新的數據在數組中的位置,若是這個位置沒有數據,會保存新數據,而且它在鏈表中的下一數據是null(e爲null),若是這個位置已經有數據,會在這個位置保存新的數據,它在鏈表中的下一個數據是老的數據(e)。

void createEntry(int hash, K key, V value, int bucketIndex) {
        //獲取當前位置的數據e,有多是null
        Entry<K,V> e = table[bucketIndex];
        //當前位置保存新的數據,歷史數據e爲新數據的鏈表相聯的數據
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
}

假如哈希值不同,會產生碰撞嗎?答案是確定的.

首先咱們看一下,hashMap中數據在桶中位置計算方式

static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }

「與」運算擴展:

參加運算的兩個數據,按二進制位進行「與」運算。

運算規則:0&0=0;0&1=0;1&0=0;1&1=1;

即:兩位同時爲「1」,結果才爲「1」,不然爲0

**舉例說明:
1&(16-1)=1
17&(16-1)=1**

因此咱們能夠理解,當桶的容量是固定的時候,負載因子越大,最大實際容量就會越大,須要保存的數據就越多。「碰撞」的狀況出現的狀況就會更多,增長了HashMap中鏈表結構中的數據,下降查詢的效率。增長了空間利用率。

相反,當負載因子越小的時候,桶的實際容量就會越小,能夠存的數據越少,碰撞狀況減小,減小鏈表數據,增長查詢效率。下降了空間利用率。

6、HashMap的簡單使用

clipboard.png

搞定~

喜歡的小夥伴記得關注一下~能夠免費領取學習資料~

clipboard.png

相關文章
相關標籤/搜索