1. HashMap由數組+鏈表組成的,數組是HashMap的主體,鏈表則是主要爲了解決哈希衝突而存在的,如果定位到的數組位置不含鏈表(當前entry的next指向null),
那麼對於查找,添加等操作很快,僅需一次尋址即可;如果定位到的數組包含鏈表,對於添加操作,其時間複雜度爲O(n),首先遍歷鏈表,存在即覆蓋,否則新增;
對於查找操作來講,仍需遍歷鏈表,然後通過key對象的equals方法逐一比對查找。所以,性能考慮,HashMap中的鏈表出現越少,性能纔會越好
2. HashMap的主幹是一個Entry數組。Entry是HashMap的基本組成單元,每一個Entry包含一個key-value鍵值對
//HashMap的主幹數組,可以看到就是一個Entry數組,初始值爲空數組{},主幹數組的長度一定是2的次冪
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
Entry是HashMap中的一個靜態內部類
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;//存儲指向下一個Entry的引用,單鏈表結構
int hash;//對key的hashcode值進行hash運算後得到的值,存儲在Entry,避免重複計算
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
3. 其他幾個重要字段
//實際存儲的key-value鍵值對的個數
transient int size;
//閾值,當table == {}時,該值爲初始容量(初始容量默認爲16);當table被填充了,也就是爲table分配內存空間後,threshold一般爲 capacity*loadFactory。HashMap在進行擴容時需要參考threshold
int threshold;
//負載因子,代表了table的填充度有多少,默認是0.75
final float loadFactor;
//用於快速失敗,由於HashMap非線程安全,在對HashMap進行迭代時,如果期間其他線程的參與導致HashMap的結構發生變化了(比如put,remove等操作),需要拋出異常ConcurrentModificationException
transient int modCount;
4. HashMap中比較重要的put(K key, V value)方法源碼解析
public V put(K key, V value) {
//如果table數組爲空數組{},進行數組填充(爲table分配實際內存空間),入參爲threshold,此時threshold爲initialCapacity 默認是1<<4(24=16)
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key爲null,存儲位置爲table[0]或table[0]的衝突鏈上
if (key == null)
return putForNullKey(value);
int hash = hash(key);//對key的hashcode進一步計算,確保散列均勻
int i = indexFor(hash, table.length);//獲取在table中的實際位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//如果該對應數據已存在,執行覆蓋操作。用新value替換舊value,並返回舊value
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;//保證併發訪問時,若HashMap內部結構發生變化,快速響應失敗
addEntry(hash, key, value, i);//新增一個entry
return null;
}
5. HashMap中比較重要的get(Object key)方法
public V get(Object key) {
//如果key爲null,則直接去table[0]處去檢索即可。
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
/**
* get方法的實現相對簡單,key(hashcode)-->hash-->indexFor-->最終索引位置
* 找到對應位置table[i],再查看是否有鏈表,遍歷鏈表,通過key的equals方法比對查找對應的記錄
*/
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//通過key的hashcode值計算hash值
int hash = (key == null) ? 0 : hash(key);
//indexFor (hash&length-1) 獲取最終數組索引,然後遍歷鏈表,通過equals方法比對找出對應記錄
for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
6. HashMap中重要的一點:HashMap存儲自定義類型鍵值的時候,如果自定義對象作爲key存在,這時要保證對象唯一,必須要同時重寫對象中的equals方法和hashcode方法