以下源碼基於Android中改造過後的HashMap
0.HashMap中的關鍵變量
- MINIMUN_CAPACITY = 4 (最小容量)
- MAXIMUN_CAPACITY = 1 << 30 ; (最大容量)
- private static final Entry[] EMPTY_TABLE= new HashMapEntry[MINIMUM_CAPACITY >>> 1]; 這裏的這個就是hash表,是一種數組鏈表結構(和字典一樣),默認的容量大小爲4>>1,也就是2
- DEFAULT_LOAD_FACTOR 負載因子,默認是0.75F
- modCount 修改次數
- threshold 閥值
- 其他
1、HashMapEntry
看HashMapEntry的構造函數。
HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = next;
}
從中可以看出,這是一個單鏈表的數據結構,存有key、value、hash值以及下一個節點。
2、HashMap的的初始化
- HashMap()
- HashMap(int capacity)
- HashMap(int capacity,float loadFactor)
第三個構造方法,直接調用的是第一個構造方法,並對loadFactor進行判斷(然而,這並沒有什麼吊用)
那麼。我們就來看HashMap的代碼吧。
public HashMap() {
table = (HashMapEntry<K, V>[]) EMPTY_TABLE;
threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
}
- 這裏的這個table是什麼呢?因爲這是個數組,而數組中每個元素都是單鏈表,所有,就構成table的樣式了。
- threshold = -1,看註釋是說,首次調用替換掉EMPTY_TABLE.
3、添加數據
- put(K key, V value)
- putAll(Map
3.1、put(K key,V value)
@Override public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
// No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;
}
- 若key爲null,則將值存放在entryForNullKey(當然會做一些處理)
- 算出key對應的hash值
- 根據hash值計算出index值取到對應的鏈表,如果存在hash值相等並且key值相等的的Entry,就修改value值,並返回舊的value值
- 如果size++大於了閥值,對進行擴容並從新計算index值
- 插入一個新的Entry,並返回null
下面來對上面的1,4,5進行說明
3.1.1 putValueForNullKey操作
相對應的源碼如下。
private V putValueForNullKey(V value) {
HashMapEntry<K, V> entry = entryForNullKey;
if (entry == null) {
addNewEntryForNullKey(value);
size++;
modCount++;
return null;
} else {
preModify(entry);
V oldValue = entry.value;
entry.value = value;
return oldValue;
}
}
這裏對應的操作也很簡單,如果當前entryForNullKey爲null的話,就添加一個,不爲null,就修改值
3.1.2 doubleCapacity() 擴容
擴容部分源代碼較長,咱們分段來看。
HashMapEntry<K, V>[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
return oldTable;
}
int newCapacity = oldCapacity * 2;
HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
if (size == 0) {
return newTable;
}
- 如果到最大容量了,直接返回
- 將容量設置爲原來的2倍
- 製造一個table,(ps:製造的時候會將閥值設置爲3/4,(容量>>1) + (容量>>2),>>1 相當於/2,>>2 相當於/4)
- 如果size(原先存儲的數目)爲0,直接返回
for (int j = 0; j < oldCapacity; j++) {
/*
* Rehash the bucket using the minimum number of field writes.
* This is the most subtle and delicate code in the class.
*/
HashMapEntry<K, V> e = oldTable[j];
if (e == null) {
continue;
}
int highBit = e.hash & oldCapacity;
HashMapEntry<K, V> broken = null;
newTable[j | highBit] = e;
for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {
int nextHighBit = n.hash & oldCapacity;
if (nextHighBit != highBit) {
if (broken == null)
newTable[j | nextHighBit] = n;
else
broken.next = n;
broken = e;
highBit = nextHighBit;
}
}
if (broken != null)
broken.next = null;
}
return newTable;
- 上面代碼的就是將原table中每一處對應的鏈表取出來,並且從新散列
3.1 addNewEntry添加新的Entry
table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
其中table[index]就是一個單鏈表,這裏就是生成一個HashMapEntry並將其插入到index處的,當然,我們還需要看一下生成的構造方法。
HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = next;
}
結合邏輯可以知道,我們可以看的出,每次是在鏈表的頭部進行數據插入的。
3.2、putAll
@Override public void putAll(Map<? extends K, ? extends V> map) {
ensureCapacity(map.size());
super.putAll(map);
}
- ensureCapacity,確保容量(這裏就是進行容量檢查,不夠擴容,具體的細節就不說了)
- 調用父類去put數據
在這裏我們就需要明白父類的實現了。
public void putAll(Map<? extends K, ? extends V> map) {
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
從中可以看出,如果穿進來的是另一個HashMap的話,就會將這個HashMap中的Entry挨個加入到運來的HashMap中。
4、get取值
取值的過程因爲在散列與散列碼中,有提到過,所以這裏就不多說了。
5、總結
HashMap查詢快速的原因就在於hashtable的思想。就像字典一樣。
本博文中只是簡單的介紹了下HashMap。當然HaspMap中還有許多值得我們去思考的問題,諸如:
- 負載因子 爲什麼是0.75?
- 初始容量爲什麼在Java8中改成2了
- 散列時index的算法
- 爲什麼從新散列是那樣求index的
- 其他
等等,這些問題,每一個都值得我們去好好地研究。