HashMap的四個(gè)關(guān)注點(diǎn)
關(guān)注點(diǎn) | 結(jié)論 |
---|---|
hashMap鍵值是否可以為空 | key和value都允許為null |
hashMap是否允許重復(fù)數(shù)據(jù) | key重復(fù)會(huì)覆蓋罪治,value允許重復(fù) |
hashMap是否有序 | 無序 |
是否線程安全 | HashMap不是線程安全的竭翠,(Hashtable是線程安全的,其方法被synchronized包裹) |
HashMap簡(jiǎn)介
- HashMap 是一個(gè)散列表琼锋,它存儲(chǔ)的內(nèi)容是鍵值對(duì)(key-value)映射棵红。
- HashMap 繼承于AbstractMap,實(shí)現(xiàn)了Map观蜗、Cloneable臊恋、java.io.Serializable接口。繼承關(guān)系如下
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
- HashMap 的實(shí)現(xiàn)不是同步的墓捻,這意味著它不是線程安全的抖仅。
注意:它的key、value都可以為null砖第。
此外撤卢,HashMap中的映射不是有序的。
HashMap 的實(shí)例有兩個(gè)參數(shù)影響其性能:“初始容量” 和 “加載因子”梧兼。容量 是哈希表中桶的數(shù)量放吩,初始容量 只是哈希表在創(chuàng)建時(shí)的容量。加載因子 是哈希表在其容量自動(dòng)增加之前可以達(dá)到多滿的一種尺度羽杰。當(dāng)哈希表中的條目數(shù)超出了加載因子與當(dāng)前容量的乘積時(shí)渡紫,則要對(duì)該哈希表進(jìn)行 rehash 操作(即重建內(nèi)部數(shù)據(jù)結(jié)構(gòu)),從而哈希表將具有大約兩倍的桶數(shù)考赛。
通常惕澎,默認(rèn)加載因子是 0.75, 這是在時(shí)間和空間成本上尋求一種折衷。加載因子過高雖然減少了空間開銷颜骤,但同時(shí)也增加了查詢成本(在大多數(shù) HashMap 類的操作中唧喉,包括 get 和 put 操作,都反映了這一點(diǎn))复哆。在設(shè)置初始容量時(shí)應(yīng)該考慮到映射中所需的條目數(shù)及其加載因子欣喧,以便最大限度地減少 rehash 操作次數(shù)。如果初始容量大于最大條目數(shù)除以加載因子梯找,則不會(huì)發(fā)生 rehash 操作唆阿。
HashMap有四個(gè)構(gòu)造方法,如下圖
HashMap數(shù)據(jù)結(jié)構(gòu)
畫了個(gè)示意圖锈锤,如下驯鳖,左邊的數(shù)組索引是根據(jù)key的hash值計(jì)算得到闲询,不同hash值有可能產(chǎn)生一樣的索引,即哈希沖突浅辙,此時(shí)采用鏈地址法處理哈希沖突扭弧,即將所有索引一致的節(jié)點(diǎn)構(gòu)成一個(gè)單鏈表;
相比于之前的版本记舆,jdk1.8在解決哈希沖突時(shí)有了較大的變化鸽捻,當(dāng)鏈表長(zhǎng)度大于閾值(默認(rèn)為8)時(shí),將鏈表轉(zhuǎn)化為紅黑樹泽腮,以減少搜索時(shí)間御蒲。原本Map.Entry接口的實(shí)現(xiàn)類Entry改名為了Node。轉(zhuǎn)化為紅黑樹時(shí)改用另一種實(shí)現(xiàn)TreeNode诊赊。
HashMap源碼分析
// 序列號(hào)
private static final long serialVersionUID = 362498820763181265L;
// 默認(rèn)的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默認(rèn)的填充因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 當(dāng)桶(bucket)上的結(jié)點(diǎn)數(shù)大于這個(gè)值時(shí)會(huì)轉(zhuǎn)成紅黑樹
static final int TREEIFY_THRESHOLD = 8;
// 當(dāng)桶(bucket)上的結(jié)點(diǎn)數(shù)小于這個(gè)值時(shí)樹轉(zhuǎn)鏈表
static final int UNTREEIFY_THRESHOLD = 6;
// 桶中結(jié)構(gòu)轉(zhuǎn)化為紅黑樹對(duì)應(yīng)的table的最小大小
static final int MIN_TREEIFY_CAPACITY = 64;
// 存儲(chǔ)元素的數(shù)組厚满,總是2的冪次倍
transient Node<k,v>[] table;
// 存放具體元素的集
transient Set<map.entry<k,v>> entrySet;
// 存放元素的個(gè)數(shù),注意這個(gè)不等于數(shù)組的長(zhǎng)度碧磅。
transient int size;
// 每次擴(kuò)容和更改map結(jié)構(gòu)的計(jì)數(shù)器
transient int modCount;
// 臨界值 當(dāng)實(shí)際大小(容量*填充因子)超過臨界值時(shí)碘箍,會(huì)進(jìn)行擴(kuò)容
int threshold;
// 填充因子,默認(rèn)值是7.5
final float loadFactor;
構(gòu)造方法
/**
* 根據(jù)自定義的初始化容量和加載因子構(gòu)建一個(gè)空的HashMap.
*/
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);
}
/**
* 使用初始化容量和默認(rèn)加載因子(0.75).
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 使用默認(rèn)初始化大小(16)和默認(rèn)加載因子(0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* 用已有的Map構(gòu)造一個(gè)新的HashMap.
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
數(shù)據(jù)存取
put方法
//根據(jù)key值計(jì)算出hashcode鲸郊,
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
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;
// table未初始化或者長(zhǎng)度為0丰榴,進(jìn)行擴(kuò)容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// (n - 1) & hash 確定元素存放在哪個(gè)桶中,桶為空严望,新生成結(jié)點(diǎn)放入桶中(此時(shí)多艇,這個(gè)結(jié)點(diǎn)是放在數(shù)組中)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 桶中已經(jīng)存在元素
else {
Node<K,V> e; K k;
// 比較桶中第一個(gè)元素(數(shù)組中的結(jié)點(diǎn))的hash值相等,key相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 將第一個(gè)元素賦值給e像吻,用e來記錄
e = p;
// hash值不相等,即key不相等复隆;為紅黑樹結(jié)點(diǎn)
else if (p instanceof TreeNode)
// 放入樹中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 為鏈表結(jié)點(diǎn)
else {
// 在鏈表最末插入結(jié)點(diǎn)
for (int binCount = 0; ; ++binCount) {
// 到達(dá)鏈表的尾部
if ((e = p.next) == null) {
// 在尾部插入新結(jié)點(diǎn)
p.next = newNode(hash, key, value, null);
// 結(jié)點(diǎn)數(shù)量達(dá)到閾值拨匆,轉(zhuǎn)化為紅黑樹
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
// 跳出循環(huán)
break;
}
// 判斷鏈表中結(jié)點(diǎn)的key值與插入的元素的key值是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 相等,跳出循環(huán)
break;
// 用于遍歷桶中的鏈表挽拂,與前面的e = p.next組合惭每,可以遍歷鏈表
p = e;
}
}
// 表示在桶中找到key值、hash值與插入元素相等的結(jié)點(diǎn)
if (e != null) {
// 記錄e的value
V oldValue = e.value;
// onlyIfAbsent為false或者舊值為null
if (!onlyIfAbsent || oldValue == null)
//用新值替換舊值
e.value = value;
// 訪問后回調(diào)
afterNodeAccess(e);
// 返回舊值
return oldValue;
}
}
// 結(jié)構(gòu)性修改
++modCount;
// 實(shí)際大小大于閾值則擴(kuò)容
if (++size > threshold)
resize();
// 插入后回調(diào)
afterNodeInsertion(evict);
return null;
}
在上述代碼中的第二行注釋中亏栈,table未初始化或者長(zhǎng)度為0台腥,進(jìn)行擴(kuò)容,會(huì)調(diào)用resize()方法绒北,代碼如下
final Node<K,V>[] resize() {
// 當(dāng)前table保存
Node<K,V>[] oldTab = table;
// 保存table大小
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 保存當(dāng)前閾值
int oldThr = threshold;
int newCap, newThr = 0;
// 之前table大小大于0
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
}
// 之前閾值大于0
else if (oldThr > 0)
newCap = oldThr;
// oldCap = 0并且oldThr = 0闷游,使用缺省值(如使用HashMap()構(gòu)造函數(shù)峻汉,之后再插入一個(gè)元素會(huì)調(diào)用resize函數(shù)贴汪,會(huì)進(jìn)入這一步)
else {
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;
// 之前的table已經(jīng)初始化過
if (oldTab != null) {
// 復(fù)制元素,重新進(jìn)行hash
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
// 將同一桶中的元素根據(jù)(e.hash & oldCap)是否為0進(jìn)行分割休吠,分成兩個(gè)不同的鏈表扳埂,完成rehash
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;
}
說明:進(jìn)行擴(kuò)容,會(huì)伴隨著一次重新hash分配瘤礁,并且會(huì)遍歷hash表中所有的元素阳懂,是非常耗時(shí)的。在編寫程序中柜思,要盡量避免resize希太。
get方法
public V get(Object key) {
Node<K,V> e;
//這里直接調(diào)用的getNode方法
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;
// table已經(jīng)初始化,長(zhǎng)度大于0酝蜒,根據(jù)hash尋找table中的項(xiàng)也不為空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 桶中第一項(xiàng)(數(shù)組元素)相等
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 桶中不止一個(gè)結(jié)點(diǎn)
if ((e = first.next) != null) {
// 為紅黑樹結(jié)點(diǎn)
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;
}
putAll方法
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
/**
* Implements Map.putAll and Map constructor
*
* @param m the map
* @param evict false when initially constructing this map, else
* true (relayed to method afterNodeInsertion).
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict); // put核心方法
}
}
}
參考文章
圖解集合HashMap :http://www.importnew.com/25049.html)http://www.importnew.com/25049.html