理解
- 散列算法:把多個數據用少量數據標記出來
- hashing(哈希):標記是hashcode的散列算法
HashMap的結構是
包含多個HashCode的數組,每個HashCode對應一個Bucket(桶),每個桶是一個LinkList(鏈表),鏈表裏面存儲的是多個Entry(鍵值對+next+hash值)
數組中存取值 通過如下算法:
// 存儲時:
int hash = key.hashCode();
int index = hash % Entry[].length;
Entry[index] = value;
// 取值時:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];
HashMpa.put(對象)
對象就是一個Entry,程序員控制鍵值對的內容,next和hash會自動生成
- 碰撞:當系統給兩個Entry生成的hash相同時就會發生碰撞,最後Entry,會存儲到相同hash對應的鏈表裏,其中 next,就是標記多個相同hash的Entry 之間的鏈表連接關係的。
next和hash是怎麼自動生成的?
- put()
當程序員put一個Entry時,先使用Hash函數計算出下數組下標,確定這個Entry插入到hashcode數組的哪個位置
public V put(K key, V value) {
if (key == null)
return putForNullKey(value); //null總是放在數組的第一個鏈表中
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
//遍歷鏈表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果key在鏈表中已存在,則替換爲新value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //參數e, 是Entry.next
//如果size超過threshold,則擴充table大小。再散列
if (size++ >= threshold)
resize(2 * table.length);
}
- get()
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
//先定位到數組元素,再遍歷該元素處的鏈表
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.equals(k)))
return e.value;
}
return null;
}
根據Key值做一次Hash映射,算出其對應的數組下標值,
碰撞問題,同一個位置的鏈表裏可能存在多個Entry,這時就要從對應鏈表的頭節點開始,一個個向下查找,直到找到對應的
Key值,這樣就獲得到了所要查找的鍵值對
-
hashcode數組的長度?
有一個初始長度16,每當觸發到75%,長度擴大一倍,然後把之前的數組全部數據移動到新數組中。 -
hashmap死鎖
在多線程的情況下,當重新調整HashMap大小的時候,就會存在條件競爭,因爲如果兩個線程都發現HashMap需要重新調整大小了,它們會同時試着調整大小
使用hashtable(完全同步)和currentHashMap(分片同步)等線程安全的可以避免死鎖。