java.util.HashMap實現Map接口。此實現提供所有可選的映射操作,並允許null值和null鍵。(HashMap類大致等同於Hashtable,只是它不同步並且允許空值。)這個類不保證映射的順序;特別是,它不保證順序隨時間保持不變。
HashMap爲基本操作(get和put)提供恆定的時間性能,假設哈希函數在存儲桶之間正確地分散元素。集合視圖上的迭代需要與HashMap實例的“容量”(bucket的數量)加上其大小(鍵值映射的數量)成比例的時間。因此,如果迭代性能很重要的話,不要設置太高的初始容量(或者太低的負載係數)。
HashMap的一個實例有兩個影響其性能的參數:初始容量和負載因子。容量是哈希表中的存儲桶數,初始容量只是創建哈希表時的容量。負載因子是在哈希表的容量自動增加之前允許獲得的滿容量的度量。當哈希表中的條目數超過負載因子和當前容量的乘積時,哈希表將重新hash(即重建內部數據結構),以便哈希表具有大約兩倍的存儲桶數。
一般來說,默認加載因子(0.75)在時間和空間成本之間提供了一個很好的折中。較高的值會減少空間開銷,但會增加查找成本(反映在HashMap類的大多數操作中,包括get和put)。在設置初始容量時,應考慮map中的預期條目數及其負載係數,以儘量減少rehash操作次數。如果初始容量大於最大條目數除以負載係數,則不會發生rehash操作。
哈希表
哈希表是由一塊地址連續的數組空間構成的,其中每個數組都是一個鏈表,數組的作用在於快速尋址查找,鏈表的作用在於快速插入和刪除元素,因此,哈希表可以被認爲就是鏈表的數組。
哈希衝突
多個Key對象,通過計算得到了同一個值的情況。
h1 =hash(key1)&(length -1)
h2 =hash(key2)&(length -1)
h3 =hash(key3)&(length -1)
如果出現下面的情況,就是衝突了
h1 == h2 == h3
解決衝突的一般辦法
-
開發地址法:當衝突時,通過某種方法查找下一個空位,並將數據填入,而不再用哈希函數得到的數組index,某種方法,可以有很多種,有挨着找(線性探測),跳着找(平方探測等)。
-
再哈希法:首先定義很多hash函數,當發生哈希衝突時,就再使用另一個hash函數計算另一個哈希值,直到不衝突爲止。
-
鏈地址法:將所有哈希地址一樣的的元素,構成一個稱爲鏈表,因而查找、插入和刪除主要在鏈表中進行,鏈地址法適用於經常進行插入和刪除的情況。
HashMap解決衝突的辦法是:鏈地址法。
類名
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
變量(部分)
//默認初始容量-必須是2的冪。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量 1<<30.
static final int MAXIMUM_CAPACITY = 1 << 30;
//負載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//閾值
static final int TREEIFY_THRESHOLD = 8;
/**
* 在首次使用時初始化,並根據需要調整大小。分配時,長度總是2的冪。
*(在某些操作中,我們還允許長度爲零,以允許當前不需要的引導機制。)
*/
transient Node<K,V>[] table;
/**
* 保存緩存的entrySet
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* map的長度
*/
transient int size;
/**
* 此字段用於使HashMap的集合視圖上的迭代器 fail-fast
*/
transient int modCount;
/**
* 下一個要調整大小的大小值(容量*負載因子).
*/
int threshold;
/**
* 負載因子
*/
final float loadFactor;
put(K key, V value)
-
對key的hashCode()做hash,然後再計算index;
-
如果沒碰撞直接放到bucket裏;
-
如果碰撞了,以鏈表的形式存在buckets後;
-
如果碰撞導致鏈表過長(大於等於TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹;
-
如果節點已經存在就替換old value(保證key的唯一性)
-
如果bucket滿了(超過load factor*current capacity),就要resize。
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;
// tab爲空則創建
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 計算index,並對null做處理
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 節點key存在,直接覆蓋value
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);
//鏈表長度大於8轉換爲紅黑樹進行處理 TREEIFY_THRESHOLD = 8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// key已經存在並相等,不往鏈表加值
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
((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;
}
resize()
初始化或加倍表大小。如果爲空,則根據字段閾值中保留的初始容量目標進行分配。然而又因爲我們使用的是2次冪的擴展(指長度擴爲原來2倍),所以,元素的位置要麼是在原位置,要麼是在原位置再移動2次冪的位置。
final Node<K,V>[] resize() {
// 保存當前table
Node<K,V>[] oldTab = table;
// 保存當前table的容量
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 保存當前閾值
int oldThr = threshold;
// 初始化新的table容量和閾值
int newCap, newThr = 0;
// oldCap大於 0 代表原來的 table 表非空
if (oldCap > 0) {
//如果舊table容量已超過最大容量,更新閾值爲最大值
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 容量翻倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 閾值翻倍
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) //oldCap小於等於0且oldThr大於0表示新創建了一個HashMap
newCap = oldThr;
else { // new HashMap()
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 新閾值爲0
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 初始化table
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
//把oldTab中的節點reHash到newTab 中去
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 若節點是單個節點,直接在newTab中進行重定位
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 若節點是TreeNode節點則進行 紅黑樹的 rehash
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { //若是鏈表,進行鏈表的 rehash
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
get(Object key)
-
bucket裏的第一個節點,直接命中;
-
如果有衝突,則通過key.equals(k)去查找對應的entry,若爲樹,則在樹中通過key.equals(k)查找;若爲鏈表,則在鏈表中通過key.equals(k)查找。
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) {
// 直接命中
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)// 在樹中get
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {// 在鏈表中get
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
HashMap實現多線程
Map map = Collections.synchronizedMap(new HashMap<>());
更多精彩內容請關注微信公衆號: