java基礎解析系列(三)---HashMap
java基礎解析系列
- java基礎解析系列(一)---String、StringBuffer、StringBuilder
- java基礎解析系列(二)---Integer
- java基礎解析系列(三)---HashMap
- 這是我的博客目錄,歡迎閱讀
基本概念
- 節點:
Node<Key,Value>
,存放key和value
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
- 鍵值對數組:
Node<K,V>[] table
- 加載因子
- 容量 :Node數組的長度
- 大小:hashmap存放的Node的數目
- 閾值:容量*加載因子
工作原理
- 創建一個長度為2的次冪的node數組
- put的時候,計算key的hash值,將hash值與長度-1進行與運算
- 如果數組該下標的位置為空,直接存放,如果不為空,判斷節點是否為樹節點,如果是的話按紅黑樹的方式存入,否則按照鏈表的形式存入
- 當hashmap的節點數目大於閾值的時候,將會重新構造hashmap,而這種操作是費時的操作,所以建議初始化一個合適的容量
域
- 默認容量,2的四次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
- 默認加載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
- node 數組
transient Node<K,V>[] table;
- 鍵值對數目,不是table的長度
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
- 閾值
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
//閾值
int threshold;
- 加載因子
/**
* The load factor for the hash table.
*
* @serial
*/
//加載因子
final float loadFactor;
構造方法
- 傳入初始容量和加載因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
- 傳入初始容量,使用默認的加載因子
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
- 無參數,默認容量和加載因子
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
)
- 容量必須是2的n次方,當你傳入的參數不符合條件,會有方法找到一個大於這個參數的最小的2的n次方數(比如大於6的最小2的n次冪是8),
put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
- 直接用偽代碼表示
put()
{
index=[hash(key)&(captity-1)]----下標的最大值為captity-1,進行與運算后最終的結果小於等於最大下標
if(table[index])==null)
直接添加node
else
{
if(p是treenode)
{
直接將節點添加到紅黑樹
}
else
{
如果不是紅黑樹是鏈表
if(p的鍵值==key)
覆蓋value
else
{
遍歷鏈表:
{
if(有對應的key)
{
覆蓋value
break;
}
}
遍歷完成后沒有發現對應的key
{
添加到鏈表
if(鏈表長度>8)
{
將鏈表轉化為紅黑樹
}
}
}
}
if(大小大於閾值)
{
容量加倍,重新構造
}
}
}
get方法
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果鏈表的第一個節點是的鍵和要查找的鍵相等,那么返回該node
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果不是的,看該節點是不是樹節點,是的話,用樹的方法查找節點,如果不是的按鏈表的方式查找
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
為什么長度設置為2的n次方
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
- 存放node到table數組的時候,他的下標是通過(n-1)&hash計算出來的(數組長度-1 和 key的hash的值相與,最后結果小於等於長度-1),n為table的長度。
- 當長度為2的n次冪的時候,(n-1)&hash==hash%n,而前者是位運算,速度會快很多
負載因子
- 負載因子較大,說明閾值較大,也就意味着可能發生更多的沖突
- 負載因子較小,說明閾值較小,也就意味着可能會更少的沖突
- 發生沖突的時候,會降低hashmap的查找速度,所以當要求更少的內存的時候可以增加負載因子,當要求更高的查找速度的時候,可以減少負載因子。
- 默認的參數是平衡的選擇,所以不建議修改
我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)
作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。