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方法判斷對象的想等性。
參考文案鏈接