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是數組+鏈表的數據結構。
- 當向HashMap中插入鍵值對的時候,首先會計算出key的hash值,然後根據hash值插入到數組相應的數組下標處。
- 一個數組元素=一個鍵值對=一個鏈表的頭節點(hash,key,value,next)next表示下一個節點對象。
- 當數組下標中有元素的時候,則需要將原元素移動到鏈表中,衝突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.7數據結構爲數組+鏈表的實現方式;1.8數據結構爲數組+鏈表+紅黑樹的實現方式。
- JDK1.8中resize()方法在表爲空時,創建表;在表不爲空時,擴容;而JDK1.7resize()方法只負責擴容,inflateTable()負責創建表。
- 1.7新增節點是採用頭插法,而1.8是採用尾插法
- 在擴容的時候:1.7在插入數據之前擴容,而1.8插入數據成功之後擴容。
圖片轉載自:https://blog.csdn.net/carson_ho/article/details/79373134
關於HashMap的源碼詳細分析:https://blog.csdn.net/carson_ho/article/details/79373134