public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
HashMap繼承了AbstractMap,Map,Cloneable,Serializable,表示是映射,存儲Key-Value,可以被克隆,可以序列化
一、常量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 // 默認容量是16
static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量是2的30次冪
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默認負載因子是0.75
static final int UNTREEIFY_THRESHOLD = 6; // 紅黑樹轉為鏈表閾值,當HashMap擴容時,如果發(fā)現(xiàn)鏈表長度小于6,則由紅黑樹退化為鏈表
static final int TREEIFY_THRESHOLD = 8; // 鏈表轉為紅黑樹的閾值,在HashMap擴容時,如果發(fā)現(xiàn)鏈表長度大于8,則將鏈表轉化為紅黑樹
static final int MIN_TREEIFY_CAPACITY = 64; // 當HashMap中的容量大于64時,才考慮樹形化,否則桶內元素過多時,考慮的時擴容而不是樹形化
二、變量
transient Node<K,V>[] table; // Node數(shù)組table,即哈希桶數(shù)組
transient int size; // 實際存儲的key-value數(shù)量
transient Set<Map.Entry<K,V>> entrySet; // 待定??????
int threshold; // HashMap擴容閾值,當size >= threshold時,HashMap就會擴容,threshold = 容量 * 負載因子,threshold越大,容納的鍵值對越多
final float loadFactor; // 負載因子
transient int modCount; // 記錄HashMap發(fā)生變化的次數(shù),主要用來迭代的快速失???????
三、存儲結構

HashMap的存儲結構是數(shù)組+鏈表+紅黑樹(JDK8)實現(xiàn)的,哈希沖突導致的鏈表過長時,會嚴重影響HashMap的性能,如查找和刪除,JDK8引入紅黑樹,當鏈表過長時,將鏈表轉化為紅黑樹,利用紅黑樹增刪改查的高性能提高HashMap的性能,
一、構造函數(shù)
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
默認負載因子是0.75,默認容量16,必須是2的冪public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
指定HashMap的容量,負載因子是默認0.75-
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);
}
指定HashMap的容量和負載因子,HashMap的最大容量MAXIMUM_CAPACITY = 1 << 30,即2的30次方
二、數(shù)組
transient Node<K,V>[] table;
HashMap有一個內部靜態(tài)類:Node,實現(xiàn)了Map.Entry接口,本質是一個key-value,next表示是一個鏈表節(jié)點,用來處理哈希沖突,hash用來確定key-value保存在table數(shù)組的哪個位置。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
四、具體功能之如何根據key值計算table數(shù)組的索引
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
key == null的時候,key的hash值是0,key != null時,hash值是key.hashCode與key.hashCode >>>16異或的值(無符號右移,高位補0)
hash = hash(key);
i = (table.length - 1) & hash
索引等于hash按位與table.length-1,選擇table.length-1這樣做的好處是因為table.length總是2的N次冪,計算的到i總是在數(shù)組length范圍內
五、具體功能之put方法
1.根據key計算table位置索引i
2.如果該位置table[i] == null,new Node直接保存
3.如果該位置table[i] != null,如果是Node節(jié)點,遍歷鏈表,如果鏈表中沒有匹配的key,則new Node插入到鏈表的尾部,如果鏈表長度大于8,將鏈表轉為紅黑樹,如果鏈表中有匹配的key,覆蓋原來的value
4.如果該位置table[i] != null,如果是TreeNode紅黑樹節(jié)點,則new TreeNode插入到紅黑樹
5.插入成功后,判斷實際存在的鍵值對size是否大于threshold,如果大于,進行擴容
六、具體功能之get方法
public V get(Object key)
1.根據key的hash,計算位置索引i=(table.length-1) & hash
2.首先取該位置處的第一個Node first
3.如果fist是紅黑樹節(jié)點,則查找紅黑樹中匹配的key
4.如果是鏈表節(jié)點,則遍歷鏈表,找到匹配的key
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)
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;
}
七、具體功能之擴容
當HashMap內部不能裝載更多數(shù)據時,需要擴大HashMap容量,將擴大為原來的兩倍,原來的數(shù)據將根據新的hash值放在新的位置,出發(fā)條件時數(shù)據數(shù)量大于threshold
1.計算新的capacity,threshold,capitcity為原來容量的兩倍,如果初始容量為0,則為16
2.創(chuàng)建新的數(shù)組,容量為原capacity的兩倍
3.將原來數(shù)組的數(shù)據放到新的數(shù)組中
???????
原來的key-value會放在原來的位置和原來的位置+舊capacity中
八、面試問題
1.key和value可以為null嗎?
key=null時,計算得到的索引為0,key為null的鍵值放在table[0]為頭節(jié)點的鏈表中,value可以為null
2.key可以使用自定義類型嗎
key一般使用String,Integer類型,因為這些類型時不可變的,并且實現(xiàn)了hashCode(),equals()方法,如果使用可變類型作為key,當key發(fā)生變化后,hashCode會發(fā)生變化,導致無法找到該鍵值
3.怎么實現(xiàn)線程安全
一是使用HashTable,但是效率很低,二是使用Collections包:Collections.synchronizeMap(hashMap);三是使用ConcurrentHashMap