HashMap面試必知

HashMap是java開發中常用的一個,也是面試中幾乎必問的一個知識點,英文HashMap中涉及的知識點很多(考察面試者的java礎、數據結構、及源碼閱讀),下面就HashMap的一些知識進行詳談。

首先我們看源碼中定義的一些變量:

以上變量也是HashMap中非常重知識點。下面我們就一一進行詳解:

DEFAULT_INITIAL_CAPACITY:HashMap的初始化容量16

MAXIMUM_CAPACITY:HashMap最大容量2^30=1073741824

DEFAULT_LOAD_FACTOR:負載因子初始值0.75f,假如容量爲16,那麼當容量使用至12的時候就會開始擴容。

TREEIFY_THRESHOLD :鏈表長度大於8的時候,轉換成紅黑樹

UNTREEIFY_THRESHOLD:紅黑樹節點小於6轉換爲鏈表

MIN_TREEIFY_CAPACITY:數組大小大於64時轉換爲紅黑樹

HashMap插入過程

      

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
		//1、判斷數組是否爲空,爲空初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
		//2、不爲空,計算key的hash值,通過(n-1)&hash計算應該存放在數組中的下標index,
		//判斷table[index]是否存在數據,不存在構造node節點並將其存放在table[index]中
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
		//存在數據時
        else {
            Node<K,V> e; K k;
			//判斷key和hash值是否相等,相等的則將p值賦值給e
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
		    //不相等判斷是否屬於樹節點,屬於樹節點,則插入到紅黑樹中;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //如果不是樹節點,創建Node加入鏈表末尾,判斷鏈表的長度是否大於8,大於的話鏈表轉換爲紅黑樹。
			else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
			//如果e不爲空,說明存在重複的key值,用新值替換舊值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
				//onlyIfAbsent初始值爲false
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
		//判斷當前節點數,是否達到擴容的閥值,大於閥值則擴容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
  1. 判斷數組是否爲空,爲空則進行初始化。
  2. 不爲空,計算key的hash值,通過(n-1)&hash計算應該存放在數組中的下標index
  3. 查看table[index]是否存在數據,沒有數據就構造一個node節點存放在table[index]中;
  4. 存在數據,說明發生了hash衝突(存在兩個節點key的hash值一樣),計算判斷key是否相等,相等的話,用新的value替換原來的數據。
  5. 如果不相等,判斷當前節點類型是不是樹節點,如果是樹節點,創造樹節點插入紅黑樹中;
  6. 如果不是樹型節點,創建普通的Node加入鏈表當中;判斷鏈表的長度是否大於8,大於的話鏈表轉換爲紅黑樹。
  7. 插入完成後判斷當前節點數是否大於閥值,如果大於閥值則開始擴容。

HashMap初始化?

一般如果new HashMap不傳值,默認大小16,負載因子是0.75。提供了四種初始化的方法

HashMap()  默認大小16,負載因子是0.75

HashMap(int initialCapacity, float loadFactor)

HashMap(int initialCapacity)

HashMap(Map<? extends K, ? extends V> m)

初始化大小的函數

static final int tableSizeFor(int cap) {

          int n = cap - 1;

          n |= n >>> 1;

          System.out.println(n);

          n |= n >>> 2;

          System.out.println(n);

          n |= n >>> 4;

          System.out.println(n);

          n |= n >>> 8;

          System.out.println(n);

          n |= n >>> 16;

          System.out.println(n);

          return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

      } 

構造函數傳入初始化值initialCapacity,並不是直接設置HashMap的容量值爲initialCapacity.

而是通過上面的函數計算出一個大於等於initialCapacity的2的整數次方的最小整數。

下面給大家舉個例子以10爲例子,大於10的最小2的整數次方數是16

原始數

二進制

initialCapacity 10

0000  1010

-1

0000  1001

n >>> 1

0000  0100

  n |= n >>> 1;

0000  1101

n >>> 2

0000  1100

  n |= n >>> 2;

0000  1101

n >>> 4

0000  0000

n |= n >>> 4;

0000  1101

n >>> 8

0000  0000

n |= n >>> 8;

0000  1101

n >>> 16

0000  0000

n |= n >>> 16;

0000  1101

return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

16

HashMap初始化容量大小的參數爲10,其實HashMap的實際初始化大小爲16

Hash函數:

static final int hash(Object key) {

        int h;

        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

}

爲什麼這樣設計?

  1. 儘可能的降低hash碰撞,越分散越好;
  2. 算法的高效性,採用位運算,保證高頻操作效率。

本文有借鑑右側 https://blog.csdn.net/zhengwangzw/article/details/104889549  網址博客

 

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