一、前言
hashmap是map實現中最常使用的,具有快速訪問的優點,所以很有必要深入到原始碼去了解其實現原理。 方法
的原始碼分析。
二、hashmap的資料結構
hashmap
可以理解為由陣列和鍊錶組成的儲存結構,如圖
軸方向上是乙個陣列,
y方向是鍊錶。乙個節點的資訊包括
hash
、key
、value
和next。
static class entryimplements map.entry
三、常用方法原始碼分析
1、hashmap的主要相關變數和建構函式
static final int default_initial_capacity = 16;
static final int maximum_capacity = 1 << 30;
static final float default_load_factor = 0.75f;
transient entry table;
transient int size;
int threshold;
final float loadfactor;
default_inital_capacity:預設初始化容量,16
maximum_capacity :
最大容量,即2的
30次方
default_load_factor :
預設負載係數
entry table:
entry
型別的一維陣列,長度必須是
2的冪,也就是上圖的
table陣列
int size:
當前hashmap
的元素個數
int threshold:
擴容的臨界值,即
capacity*loadfactor
final float loadfactor:
hash
表的負載係數
public hashmap(int initialcapacity, float loadfactor)
hashmap
的初始化主要是
initialcapacity
和loadfactor
這兩個引數。前者表示上圖中
x軸方向
table
的長度,即不同
hash
的key
的長度,
後者是負載係數,擴充的臨界值
threshold=capacity*loadfactor
,當達到擴充臨界值時,
hashmap
會自動擴容。比如預設情況是
16*0.75=12
,當填充到
12時,
hashmap
會自動將
x軸方向的
table
的長度擴充到32。
hashmap
裡面的capacity
,長度是
2的冪,
即使你初始化時不是
2的冪,
它也會幫你轉為2的冪
。從上面的**可以看出來。
2、新增元素
public v put(k key, v value)
}modcount++;
addentry(hash, key, value, i);
return null;
}void addentry(int hash, k key, v value, int bucketindex)
基本思路:通過
key拿到
hash
,然後通過
hash
找到table
的索引值,找到之後比較該索引值所在列(
y軸方向 )
有沒有相同的
key,有的話就替換掉原來的
vaule
,返回舊的
value
;如果沒有相同的
key,則把當前元素加進該列的前面,即鍊錶的表頭。當容量大於等於擴容的臨界值時則擴充為原來
table
長度的2倍。
注意:hashmap
允許key
的值為null
那它是怎麼根據
hash
值返回該
hash
在table
裡面的索引呢?你可能會說,很簡單啊,取模就可以了,沒錯,但取模效率是比較低的,來看看
hashmap
裡面是怎麼實現的。
static int indexfor(int h, int length)
length,table的長度,也就是hashmap裡面的capacity,長度是2的冪,&是與運算,例如length為16,則length-1的二進位制為01111,當h為17,即10001,01111&10001=00001,通過與運算取代了取模運算,效率大大提高,這也是為什麼table的長度為2的冪。
3、獲取元素
public v get(object key)
return null;
}
基本思路:根據傳過來的
key獲取其
hash
,快速定位到
table
的索引,再遍歷該索引鍊錶,用
equals
方法找出
該元素的value。
注意:一般
table裡
的同個的索引值的元素不會太多,這關係到查詢效能的問題,當達到擴容的臨界值時會自動擴容,然後
rehash
,即重新分配所有的元素來盡可能達到雜湊均勻。但
rehash
是很耗效能的,所以如果確定
hashmap
的容量,最好一開始就初始化好。
4、刪除元素
public v remove(object key)
final entryremoveentryforkey(object key)
prev = e;
e = next;
}return e;
}
其實刪除不難,定義三個指標,乙個指向當前元素,另外兩個分別指向當前元素的前乙個和後乙個元素,這主要是鍊錶的操作。
5、擴容的實現
當達到的臨界值時,系統會呼叫
resize
函式來實現擴容。
void resize(int newcapacity)
entry newtable = new entry[newcapacity];
transfer(newtable);
table = newtable;
threshold = (int)(newcapacity * loadfactor);
}void transfer(entry newtable) while (e != null);}}
}
基本思路:新建乙個更大的陣列,然後遍歷舊陣列,修改指標,把乙個個的元素放到新陣列上,每次插入都是插到鍊錶的表頭,並把舊陣列設為
null
。不算太難,但演算法精緻,值得慢慢品味
四、小結
1、hashmap採用陣列和鍊錶組成的儲存結構,用陣列方式儲存hash、key、value和next指標構成的entry物件,對於衝突採用鍊錶的方式解決;
2、hashmap插入時是插入到鍊錶的表頭,可能要擴大陣列的容量,擴容時需要重新計算hash,並複製物件到新陣列中,即rehash;
3、hashmap是非執行緒安全的。
HashMap原始碼解析
以jdk1.8為例,hashmap是乙個用於儲存key value鍵值對的集合,每乙個鍵值對是乙個node jdk1.7叫做entry 後台是用乙個node陣列來存放資料,這個node陣列就是hashmap的主幹。這裡我們主要來分析hashmap的get和put方法。public v put k k...
hashMap 原始碼解析
這幾天跳槽 被人問得最多的問題就是基礎方面的知識.當時學習的時候有點囫圇吞棗.現在回頭把這些基本的集合類原始碼都仔細閱讀下 hashmap 用的是最頻繁的.所以問得也最多了.initcapacity 初始化的容量 loadfacotr 負載因子 主要用來計算threshold的值 threshold...
HashMap原始碼解析
預設字段 static final int default initial capacity 1 4 預設node的陣列長度 16 static final int maximum capacity 1 30 陣列的最大長度 2 30 static final float default load ...