JDK1.8 HashMap有何不一樣?

  1. HashMap到底是什麼

    基於哈希表的 Map 接口的實現。此實現提供全部可選的映射操做,並容許使用 null 值和 null 鍵。(除了非同步和容許使用 null 以外,HashMap 類與 Hashtable 大體相同。)此類不保證映射的順序,特別是它不保證該順序恆久不變。 此實現假定哈希函數將元素適當地分佈在各桶之間,可爲基本操做(get 和 put)提供穩定的性能。迭代 collection 視圖所需的時間與 HashMap 實例的「容量」(桶的數量)及其大小(鍵-值映射關係數)成比例。因此,若是迭代性能很重要,則不要將初始容量設置得過高(或將加載因子設置得過低)。java

  1. JDK1.7與JDK1.8中HashMap區別node

    1. 最重要的一點是底層結構不同,1.7是數組+鏈表,1.8則是數組+鏈表+紅黑樹結構
    2. 插入鍵值對的put方法的區別,1.8中會將節點插入到鏈表尾部,而1.7中是採用頭插
    3. jdk1.7中當哈希表爲空時,會先調用inflateTable()初始化一個數組;而1.8則是直接調用resize()擴容
    4. jdk1.7中的hash函數對哈希值的計算直接使用key的hashCode值,而1.8中則是採用key的hashCode異或上key的hashCode進行無符號右移16位的結果,避免了只靠低位數據來計算哈希時致使的衝突,計算結果由高低位結合決定,使元素分佈更均勻
    5. 插入鍵值對的put方法的區別,1.8中會將節點插入到鏈表尾部,而1.7中是採用頭插
    6. 擴容時1.8會保持原鏈表的順序,而1.7會顛倒鏈表的順序
    7. 1.7中是隻要不小於閾值就直接擴容2倍;而1.8的擴容策略會更優化,當數組容量未達到64時,以2倍進行擴容,超過64以後若桶中元素個數不小於7就將鏈表轉換爲紅黑樹,但若是紅黑樹中的元素個數小於6就會還原爲鏈表,當紅黑樹中元素不小於32的時候纔會再次擴容。
  2. JDK1.8實現原理數組

    • 數據結構存儲

HashMap是經過數組存儲全部的數據,每一個元素所存放數組的下標,是根據該存儲元素的key的Hash值與該數組的長度減去1作與運算,以下所示:
index = (length_of_array - 1) & hash_of_the_key
 數組中存放元素的數據結構使用了Node和TreeNode兩種數據結構,在單個Hash值對應的存儲元素小於8個時,默認值爲Node的單向鏈表形式存儲,當單個Hash值存儲的元素大於8個時,其會使用TreeNode的數據結構存儲。
 由於在單個Hash值對應的元素小於等於8個時,其查詢時間最差爲O(8),可是當單個Hash值對應的元素大於8個時,再經過Node的單向鏈表的方式進行查詢,速度上就會變得更慢了;這個時候HashMap就會將Node的普通節點轉爲TreeNode(紅黑樹)進行存儲,這是因爲TreeNode佔用的空間大小約爲常規節點的兩倍,可是其查詢速度能夠獲得保證,這個是經過空間換時間了。當TreeNode中包括的元素變得比較少時,爲了存儲空間的佔用,也會轉換爲Node節點單向鏈表的方式實現,它們之間能夠互相轉換的。數據結構

  • Nodeapp

    static class Node<K,V> implements Map.Entry<K,V> {
           final int hash;//當前Node的Hash值
           final K key;//當前Node的key
           V value;//當前Node的value
           Node<K,V> next;//表示指向下一個Node的指針,相同hash值的Node,經過next進行遍歷查找
    
           Node(int hash, K key, V value, Node<K,V> next) {
               this.hash = hash;
               this.key = key;
               this.value = value;
               this.next = next;
           }
           ......
    }
  • TreeNode函數

    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
           TreeNode<K,V> parent;  // red-black tree links
           TreeNode<K,V> left;
           TreeNode<K,V> right;
           TreeNode<K,V> prev;    // needed to unlink next upon deletion
           boolean red;
           TreeNode(int hash, K key, V val, Node<K,V> next) {
               super(hash, key, val, next);
           }
           ......
    }

能夠看到TreeNode使用的是紅黑樹(Red Black Tree)的數據結構,紅黑樹是一種自平衡二叉查找樹,在進行插入和刪除操做時經過特定操做保持二叉查找樹的平衡,從而得到較高的查找性能,即便在最壞狀況運行時間也是很是良好的,而且在實踐中是很是高效的,它能夠在O(log n)時間內作查找、插入和刪除等操做,這裏的n 是樹中元素的數目。
HashMap存儲結構的示意圖性能

  • Hash值的計算方法優化

    // 計算指定key的hash值,原理是將key的hash code與hash code無符號向右移16位的值,執行異或運算。
    // 在Java中整型爲4個字節32位,無符號向右移16位,表示將高16位移到低16位上,而後再執行異或運行,也
    // 就是將hash code的高16位與低16位進行異或運行。
    // 小於等於65535的數,其高16位所有都爲0,於是將小於等於65535的值向右無符號移16位,則該數就變成了
    // 32位都是0,因爲任何數與0進行異或都等於自己,於是hash code小於等於65535的key,其獲得的hash值
    // 就等於其自己的hash code。
    static final int hash(Object key) {
       int h;
       return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

Hash值計算邏輯

  • 寫入數據(put)this

    public V put(K key, V value) {
     //首先根據hash方法,獲取對應key的hash值,計算方法見後面
       return putVal(hash(key), key, value, false, true);
    }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
       Node<K,V>[] tab; Node<K,V> p; int n, i;
     //判斷用戶存放元素的數組是否爲空
       if ((tab = table) == null || (n = tab.length) == 0)
           //爲空則進行初使化,並將初使化後的數組賦值給變量tab,數組的長值賦值給變量n
           n = (tab = resize()).length;
     //判斷根據hash值與數組長度減1求與獲得的下標,
     //從數組中獲取元素並將其賦值給變量p(後續該變量p能夠繼續使用),並判斷該元素是否存在
       if ((p = tab[i = (n - 1) & hash]) == null)
           //若是不存在則建立一個新的節點,並將其放到數組對應的下標中
           tab[i] = newNode(hash, key, value, null);
       else {//根據數組的下標取到了元素,而且該元素p且不爲空,下面要判斷p元素的類型是Node仍是TreeNode
           Node<K,V> e; K k;
           //判斷該數組對應下標取到的第一值是否是與正在存入值的hash值相同、
           //key相等(多是對象,也多是字符串),若是相等,則將取第一個值賦值給變量e
           if (p.hash == hash &&
               ((k = p.key) == key || (key != null && key.equals(k))))
               e = p;
           //判斷取的對象是否是TreeNode,若是是則執行TreeNode的put方法
           else if (p instanceof TreeNode)
               e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
           else {//是普通的Node節點,
             //根據next屬性對元素p執行單向鏈表的遍歷
               for (int binCount = 0; ; ++binCount) {
                   //若是被遍歷的元素最後的next爲空,表示後面沒有節點了,則將新節點與當前節點的next屬性創建關係
                   if ((e = p.next) == null) {
                     //作爲當前節點的後面的一個節點
                       p.next = newNode(hash, key, value, null);
                     //判斷當前節點的單向連接的數量(8個)是否是已經達到了須要將其轉換爲TreeNode了
                       if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                           //若是是則將當前數組下標對應的元素轉換爲TreeNode
                           treeifyBin(tab, hash);
                       break;
                   }
                   //判斷待插入的元素的hash值與key是否與單向鏈表中的某個元素的hash值與key是相同的,若是是則退出
                   if (e.hash == hash &&
                       ((k = e.key) == key || (key != null && key.equals(k))))
                       break;
                   p = e;
               }
           }
           //判斷是否找到了與待插入元素的hash值與key值都相同的元素
           if (e != null) { // existing mapping for key
               V oldValue = e.value;
             //判斷是否要將舊值替換爲新值
               if (!onlyIfAbsent || oldValue == null)
                   //知足於未指定不替換或舊值爲空的狀況,執行將舊值替換爲新值
                   e.value = value;
               afterNodeAccess(e);
               return oldValue;
           }
       }
       ++modCount;
       if (++size > threshold)
           resize();
       afterNodeInsertion(evict);
       return null;
    }
  • 讀取數據(get)spa

    public V get(Object key) {
           Node<K,V> e;
           //根據Key獲取元素
           if ((e = getNode(hash(key), key)) == null)
               return null;
           if (accessOrder)
               afterNodeAccess(e);
           return e.value;
       }
    
       final Node<K,V> getNode(int hash, Object key) {
           Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
           //if語句的第一個判斷條件
           if ((tab = table) != null //將數組賦值給變量tab,將判斷是否爲null
               && (n = tab.length) > 0 //將數組的長值賦值給變量n
               && (first = tab[(n - 1) & hash]) != null) {//判斷根據hash和數組長度減1的與運算,計算出來的的數組下標的第一個元素是否是爲空
             //判斷第一個元素是否要找的元素,大部份狀況下只要hash值太集中,或者元素不是不少,第一個元素每每都是須要的最終元素
               if (first.hash == hash && // always check first node
                   ((k = first.key) == key || (key != null && key.equals(k))))
                   //第一個元素就是要找的元素,由於hash值和key都相等,直接返回
                   return first;
               if ((e = first.next) != null) {//若是第一元素不是要找到的元,則判斷其next指向是否還有元素
                   //有元素,判斷其是不是TreeNode
                   if (first instanceof TreeNode)
                     //是TreeNode則根據TreeNode的方式獲取數據
                       return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                   do {//是Node單向鏈表,則經過next循環匹配,找到就退出,不然直到匹配完最後一個元素才退出
                       if (e.hash == hash &&
                           ((k = e.key) == key || (key != null && key.equals(k))))
                           return e;
                   } while ((e = e.next) != null);
               }
           }
           //沒有找到則返回null
           return null;
       }
相關文章
相關標籤/搜索