原始碼分析HashMap、Hashtable、HashSet的區別
阿新 • • 發佈:2018-12-02
HashMap原始碼分析-基於JDK1.8
基本結構
1)、初始變數
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable { private static final long serialVersionUID = 362498820763181265L; //預設初始容量 static final int DEFAULT_INITIAL_CAPACITY = 16; //最大容量 static final int MAXIMUM_CAPACITY = 1073741824; //預設擴充套件因子,達到容量的一定係數就開始擴容 static final float DEFAULT_LOAD_FACTOR = 0.75F; //轉為紅黑樹結構的 static final int TREEIFY_THRESHOLD = 8; //當桶(連結串列)節點數大於這個值時會轉為紅黑樹 static final int UNTREEIFY_THRESHOLD = 6; //桶結構轉化為紅黑樹對應的table的最小大小 static final int MIN_TREEIFY_CAPACITY = 64; //儲存元素的陣列 transient HashMap.Node<K, V>[] table; //存放具體元素的值 transient Set<Entry<K, V>> entrySet; //存放元素的個數 transient int size; //每次擴容修改結構map結構的計數器 transient int modCount; //臨界值,當實際大小超過臨界值會擴容 int threshold; //載入因子 final float loadFactor; }
2)、連結串列結構
存放的是鍵值對。
static class Node<K, V> implements Entry<K, V> { final int hash; //hash值 final K key; V value; HashMap.Node<K, V> next; //下一個節點 //.......省略...... public final String toString() { return this.key + "=" + this.value; } //key的hash值與value的hash值的異或結果 public final int hashCode() { return Objects.hashCode(this.key) ^ Objects.hashCode(this.value); } .......省略........ //判斷連結串列值是否相等,key相等且value相等 public final boolean equals(Object var1) { if (var1 == this) { return true; } else { if (var1 instanceof Entry) { Entry var2 = (Entry)var1; if (Objects.equals(this.key, var2.getKey()) && Objects.equals(this.value, var2.getValue())) { return true; } } return false; } } }
3)、紅黑樹結構
繼承於LinkedHashMap,提高查詢效率。
static final class TreeNode<K, V> extends java.util.LinkedHashMap.Entry<K, V> { HashMap.TreeNode<K, V> parent; //父節點 HashMap.TreeNode<K, V> left; //左節點 HashMap.TreeNode<K, V> right; HashMap.TreeNode<K, V> prev; boolean red; //顏色標誌 TreeNode(int var1, K var2, V var3, HashMap.Node<K, V> var4) { super(var1, var2, var3, var4); } //返回根節點 final HashMap.TreeNode<K, V> root() { HashMap.TreeNode var1 = this; while(true) { HashMap.TreeNode var2 = var1.parent; if (var1.parent == null) { return var1; } var1 = var2; } } ......省略....... }
hash演算法
- 計算key的hashcode —h
- 對h無符號向右位移 16位 i
- h和i做異或運算。使得高位也可以參與hash,更大程度上減少了碰撞率。
static final int hash(Object var0) {
int var1;
return var0 == null ? 0 : (var1 = var0.hashCode()) ^ var1 >>> 16;
}
重要方法分析
1)、存放資料putVal方法
對外方法
//計算key的hash值,呼叫putval方法
public V put(K var1, V var2) {
return this.putVal(hash(var1), var1, var2, false, true);
}
final V putVal(int var1, K var2, V var3, boolean var4, boolean var5) {
HashMap.Node[] var6 = this.table;
int var8;
//陣列是否存在,或長度是否為0
if (this.table == null || (var8 = var6.length) == 0) {
//是->進行擴容
var8 = (var6 = this.resize()).length;
}
Object var7;
int var9;
//計算當前索引,判斷table[i]是否存在
if ((var7 = var6[var9 = var8 - 1 & var1]) == null) {
//不存在--->新建節點
var6[var9] = this.newNode(var1, var2, var3, (HashMap.Node)null);
} else {
Object var10;
label79: {
Object var11;
//存在---判斷key值是否在陣列中存在
if (((HashMap.Node)var7).hash == var1) {
var11 = ((HashMap.Node)var7).key;
//判斷key值是否相等
if (((HashMap.Node)var7).key == var2 || var2 != null && var2.equals(var11)) { //是 --->替換舊值
var10 = var7;
break label79;
}
}
//table[i] 是否是紅黑樹結構
if (var7 instanceof HashMap.TreeNode) {
//是--加入紅黑樹結構
var10 = ((HashMap.TreeNode)var7).putTreeVal(this, var6, var1, var2, var3);
} else {
//否--->遍歷連結串列
int var12 = 0;
while(true) {
//是否有下一個節點
var10 = ((HashMap.Node)var7).next;
if (((HashMap.Node)var7).next == null) {
//無,建立節點
((HashMap.Node)var7).next = this.newNode(var1, var2, var3, (HashMap.Node)null);
//節點數是否 >= 7
if (var12 >= 7) {
//節點大於8 ,轉為紅黑樹結構
this.treeifyBin(var6, var1);
}
break;
}
//hash判斷key是否存在
if (((HashMap.Node)var10).hash == var1) {
var11 = ((HashMap.Node)var10).key;
if (((HashMap.Node)var10).key == var2 || var2 != null && var2.equals(var11)) {
break;
}
}
//存在--->替換舊值
var7 = var10;
//節點數的計數器
++var12;
}
}
}
if (var10 != null) {
Object var13 = ((HashMap.Node)var10).value;
if (!var4 || var13 == null) {
((HashMap.Node)var10).value = var3;
}
this.afterNodeAccess((HashMap.Node)var10);
//返回舊值
return var13;
}
}
++this.modCount;
//陣列長度是否大於臨界值
if (++this.size > this.threshold) {
//擴容
this.resize();
}
this.afterNodeInsertion(var5);
return null;
}
putval呼叫流程圖如下:
HashMap儲存原理:
- 根據key計算key.hashcode = (h = hash(key) ^ h>>>16).
- 根據key.hash 計算得到桶的索引 i。
- 如果該索引位置無資料 table[i],則用該資料生成一個新的節點
- 如果該索引有資料且是一個紅黑樹,在紅黑樹中執行更新或新增操作
- 如果該索引有資料且是一個連結串列,遍歷連結串列,在連結串列結尾建立節點,如果節點數超過7 ,轉為紅黑樹,判斷key的hash值是否相等,key及value相等跳出迴圈。
2)、get方法
public V get(Object var1) {
HashMap.Node var2;
return (var2 = this.getNode(hash(var1), var1)) == null ? null : var2.value;
}
//計算key的hash值,根據key.hash取值
final HashMap.Node<K, V> getNode(int var1, Object var2) {
HashMap.Node[] var3 = this.table;
HashMap.Node var4;
int var6;
//陣列是否存在,長度 ,根據keyhash計算的索引對應的資料 table[i]
if (this.table != null && (var6 = var3.length) > 0 && (var4 = var3[var6 - 1 & var1]) != null) {
Object var7;
//根據keyhash相同找位置 ----其實查詢的是第一項資料
if (var4.hash == var1) {
var7 = var4.key;
//equals方法判斷key是否一致
if (var4.key == var2 || var2 != null && var2.equals(var7)) {
return var4;
}
}
HashMap.Node var5 = var4.next;
//存在連結串列
if (var4.next != null) {
//連結串列是紅黑樹--從紅黑樹中取
if (var4 instanceof HashMap.TreeNode) {
return ((HashMap.TreeNode)var4).getTreeNode(var1, var2);
}
//遍歷連結串列--獲取value
do {
if (var5.hash == var1) {
var7 = var5.key;
if (var5.key == var2 || var2 != null && var2.equals(var7)) {
return var5;
}
}
} while((var5 = var5.next) != null);
}
}
return null;
}
HashMap取資料原理
- 計算key的hash值
- 判斷table及根據key.hash計算的索引的table[i]值是否!=null.
- 開始查詢資料
- 查詢第一個元素,是-則返回
- 是否是紅黑樹,是—則在紅黑樹中查詢
- 遍歷連結串列查詢keyhash相等且使用equals方法相等的。
3)、resize()方法
- 當陣列大小達到臨界值或者在初始化的時候,則開始進行擴容
- 每次擴容都是原來容量的2倍
- 擴充套件後Node物件的位置要麼不變,要麼是原來位置的兩倍。
- 擴容的原理:重新建立一個
new HashMap.Node[var4]
,將老的資料複製到新的Node,並將老的置空。
區別
原理:
HashMap實現原理:JDK1.8以前是陣列+連結串列結構,jdk1.8 改為陣列+連結串列+紅黑樹結構。當連結串列的節點大於等於8的時候就會使用紅黑樹結構。
Hashtable實現原理:陣列+連結串列結構。
**HashSet實現原理:**是簡單型別的HashMap,值是固定的,只儲存key值。
主幹table是元素為連結串列的陣列,連結串列是為了解決hash衝突的,當使用key值計算的hash地址一致時,在該下標下增加連結串列,存放及查詢時需要遍歷陣列先查下標,如果該下標有連結串列還需要遍歷連結串列,通過key值的equals方法判斷取值或存值。
1、初始化
- HashMap:初始化容量為16(必須是2的倍數)
- Hashtable:初始化容量為11
兩者的載入因子都是0.75.
2、執行緒是否安全
- HashMap:執行緒不安全,如果是單執行緒操作,效率較高。如果要使HashMap執行緒安全,可以使用Collection.synchronizedMap(hashmap)進行同步。
- Hashtable:執行緒安全,內部方法都使用了
synchronized
保證執行緒安全,因此單執行緒環境下他比HashMap慢。
3、空鍵值
- HashMap:鍵值都可以為null ,key為null 時放在下標為1的位置。
- Hashtable:鍵值不可以為null
4、擴容
- HashMap:擴容大小是原大小的2倍
- Hashtable:擴容大小是*2+1。
HashSet是儲存了一個固定值的的HashMap,結構及初始化基本相同,具體實現上有區別,set實現的是Set介面,map實現的是map介面。兩者hashcode的演算法不一樣,HashMap是使用key計算hash值,hashset使用成員物件計算hash值,並使用物件的equals方法判斷物件的想等性。
參考文案連結