HashMap在JDK1.7和1.8主要區別

HashMap在JDK1.7和1.8主要區別

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

首先從聲明上來看,HashMap繼承自AbstractMap 實現了Map、Cloneable、Serializable接口,點開AbstractMap 源碼,發現AbstractMap 也實現了Map接口,那麼HashMap爲什麼繼承了AbstractMap 又要實現Map?完全無法解釋的通,其實這就是類庫設計者的寫法錯誤。可以參考:http://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete回答。

HashMap在不同的JDK版本中底層的數據結構也不同,1.7的是數組+鏈表的實現方式,而1.8變成了數組+鏈表+紅黑樹的數據結構(當鏈表的長度大於8,轉爲紅黑樹)。

1.JDK1.7

簡單描述一下HashMap的存值過程:

首先HashMap是數組+鏈表的數據結構。

  1. 當向HashMap中插入鍵值對的時候,首先會計算出key的hash值,然後根據hash值插入到數組相應的數組下標處。
  2. 一個數組元素=一個鍵值對=一個鏈表的頭節點(hash,key,value,next)next表示下一個節點對象。
  3. 當數組下標中有元素的時候,則需要將原元素移動到鏈表中,衝突hash值對應的鍵值對放入數組元素中。(這和jdk1.8不同)

在這裏插入圖片描述

2.JDK1.8

此版本下HashMap的數據結構改變成數組+鏈表+紅黑樹(當鏈表長度大於8時,鏈表會轉換爲紅黑樹實現)。

爲什麼要引入紅黑樹呢:當鏈表的長度太長時,會影響HashMap的查詢效率。時間複雜度O(n)。此時利用紅黑樹快速增刪改查的特點將時間複雜度降爲O(logn)

在這裏插入圖片描述

2.1 存儲流程

在這裏插入圖片描述

2.2 實際存儲對象

HashMap中數組的元素以及鏈表節點都是Node類實現與1.7相比(Entry)只不過是換了名字

/** 
  * Node  = HashMap的內部類,實現了Map.Entry接口,本質是 = 一個映射(鍵值對)
  * 實現了getKey()、getValue()、equals(Object o)和hashCode()等方法
  **/  

  static class Node<K,V> implements Map.Entry<K,V> {

        final int hash; // 哈希值,HashMap根據該值確定記錄的位置
        final K key; // key
        V value; // value
        Node<K,V> next;// 鏈表下一個節點

        // 構造方法
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

      /** 
        * equals()
        * 作用:判斷2個Entry是否相等,必須key和value都相等,才返回true  
        */
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

2.3 相關參數

 /** 
   * 主要參數 同  JDK 1.7 
   * 即:容量、加載因子、擴容閾值(要求、範圍均相同)
   */

  // 1. 容量(capacity): 必須是2的冪 & <最大容量(2的30次方)
  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默認容量 = 16 = 1<<4 = 00001中的1向左移4位 = 10000 = 十進制的2^4=16
  static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量 =  2的30次方(若傳入的容量過大,將被最大值替換)

  // 2. 加載因子(Load factor):HashMap在其容量自動增加前可達到多滿的一種尺度 
  final float loadFactor; // 實際加載因子
  static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默認加載因子 = 0.75

  // 3. 擴容閾值(threshold):當哈希表的大小 ≥ 擴容閾值時,就會擴容哈希表(即擴充HashMap的容量) 
  // a. 擴容 = 對哈希表進行resize操作(即重建內部數據結構),從而哈希表將具有大約兩倍的桶數
  // b. 擴容閾值 = 容量 x 加載因子
  int threshold;

  // 4. 其他
  transient Node<K,V>[] table;  // 存儲數據的Node類型 數組,長度 = 2的冪;數組的每個元素 = 1個單鏈表
  transient int size;// HashMap的大小,即 HashMap中存儲的鍵值對的數量
 

  /** 
   * 與紅黑樹相關的參數
   */
   // 1. 桶的樹化閾值:即 鏈表轉成紅黑樹的閾值,在存儲數據時,當鏈表長度 > 該值時,則將鏈表轉換成紅黑樹
   static final int TREEIFY_THRESHOLD = 8; 
   // 2. 桶的鏈表還原閾值:即 紅黑樹轉爲鏈表的閾值,當在擴容(resize())時(此時HashMap的數據存儲位置會重新計算),在重新計算存儲位置後,當原有的紅黑樹內數量 < 6時,則將 紅黑樹轉換成鏈表
   static final int UNTREEIFY_THRESHOLD = 6;
   // 3. 最小樹形化容量閾值:即 當哈希表中的容量 > 該值時,才允許樹形化鏈表 (即 將鏈表 轉換成紅黑樹)
   // 否則,若桶內元素太多時,則直接擴容,而不是樹形化
   // 爲了避免進行擴容、樹形化選擇的衝突,這個值不能小於 4 * TREEIFY_THRESHOLD
   static final int MIN_TREEIFY_CAPACITY = 64;
  

2.4 加載因子

在這裏插入圖片描述

3.總結

  1. 底層的數據結構不一樣:1.7數據結構爲數組+鏈表的實現方式;1.8數據結構爲數組+鏈表+紅黑樹的實現方式。
  2. JDK1.8中resize()方法在表爲空時,創建表;在表不爲空時,擴容;而JDK1.7resize()方法只負責擴容,inflateTable()負責創建表。
  3. 1.7新增節點是採用頭插法,而1.8是採用尾插法
  4. 在擴容的時候:1.7在插入數據之前擴容,而1.8插入數據成功之後擴容。

在這裏插入圖片描述
圖片轉載自:https://blog.csdn.net/carson_ho/article/details/79373134

關於HashMap的源碼詳細分析:https://blog.csdn.net/carson_ho/article/details/79373134

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