HashMap 詳解
下面我首先拋出以下問題,讓我們帶着這些問題開始解析 HashMap:
- JDK8 中 HashMap 有哪些改動?
- JDK8 爲什麼要使用紅黑樹?
- 爲什麼重寫對象的 Equals 方法,要重寫 HashCode方法,跟 HashMap有關係嗎?
- HashMap 是線程安全的嗎? 遇到過 ConcurrentModificationException 異常嗎?爲什麼會出現,如何解決?
- 在使用 HashMap 過程中,我們應該注意什麼?
- 手寫HashMap?
- 你知道 HashMap 的工作原理嗎?
- HashMap 中能 PUT 兩個相同的 key 嗎?爲什麼能或爲什麼不能?
- HashMap 中的鍵值可以爲 NULL?能簡單說一下原理嗎?
- HashMap 擴容機制是怎麼樣的, JDK 7與 JDK 8 有什麼不同嗎?
原先是打算寫一篇介紹,但是發現了一篇博客寫的非常棒,我就不重複造輪子了。給出鏈接Java 8系列之重新認識HashMap
結合博客,再給出文章開始給出的問題的解答
JDK 8 中 HashMap 有哪些改動?
HashMap 在原有的基礎上增加了*紅黑樹 *結構,當鏈表長度大於8會轉換爲紅黑樹進行處理,當結點數目小於 6 則會還原成鏈表。
JDK 8 爲什麼要使用紅黑樹?
首先紅黑樹遍歷的時間複雜度爲 O(logn),長度爲8 時,平均查找長度是 3 ,而此時鏈表的平均查找長度是 4,所以轉換爲樹在遍歷、查找時性能會得到提升。
不過這個答案並不嚴謹,選擇數字 8 的原因如下文所述。
0: 0.60653066
1: 0.30326533
2: 0.07581633
3: 0.01263606
4: 0.00157952
5: 0.00015795
6: 0.00001316
7: 0.00000094
8: 0.00000006
理想情況下使用隨機的哈希碼,容器中節點分佈在hash桶中的頻率遵循泊松分佈,具體可以查看泊松分佈,按照泊松分佈的計算公式計算出了桶中元素個數和概率的對照表,可以看到鏈表中元素個數爲8時的概率已經非常小,再多的就更少了,所以原作者在選擇鏈表元素個數時選擇了8,是根據概率統計而選擇的。
爲什麼重寫對象的 Equals 方法,要重寫 HashCode方法,跟 HashMap有關係嗎?
Equals 方法是用於比較對象是否相等,通過源碼可以得知, HashMap 中結點的比較是根據 key 和 value 是否同時相等判斷的。
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Person) {
Person e = (Person)o;
if (Objects.equals(id, e.getId()) &&
Objects.equals(name, e.getName()))
return true;
}
return false;
}
假設我們現在只重寫 Equals 方法,而不重寫 HashCode方法
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Person) {
Person e = (Person)o;
if (Objects.equals(id, e.getId()))
return true;
}
return false;
}
那麼這種情況下,按照代碼而言應該是,只要 Person 對象的 id 相等,則默認他們是相等的,但是當我們這麼操作
Person p1 = new Person("1", "zhangsan");
Person p2 = new Person("2", "lisi");
// 此時 p1 和 p2 是相等的
System.out.println(p1.equals(p2));
HashMap map = new HashMap();
map.put(p1, "test");
System.out.println(map.get(p2));
按照道理來說這裏應該是可以獲取到 test ,然而事實上返回的是 null,因爲在 HashMap 在進行 put() 和 get() 取出時都是通過 hash() 方法來獲取對象的 hash 值,確定數組的下標。當重寫了 hash 方法就可以確定 hash 值相同。
HashMap 是線程安全的嗎? 遇到過 ConcurrentModificationException 異常嗎?爲什麼會出現,如何解決?
不是線程安全的,需要線程安全可以採用 ConcurrentHashMap 。
ConcurrentModificationException 異常時由於 modCount 和 exceptModCount 不同,這裏使用的是 fastfial 機制,出現錯誤直接拋出異常。而 iterater 中的 remove() 方法則可以保證不出現異常。
在使用 HashMap 過程中,我們應該注意什麼?
使用HashMap時,要注意HashMap容量和加載因子的關係,這將直接影響到HashMap的性能問題。加載因子過小,會提高HashMap的查找效率,但同時也消耗了大量的內存空間,加載因子過大,節省了空間,但是會導致HashMap的查找效率降低。
如果能夠確定大小,在初始化的時候一定要給出,避免不必要的擴容操作。
手寫HashMap?
public class MyHashMap<K,V> {
// 數組的長度 默認數組長度是 16
private static final int length = 8;
// HashMap 的大下
private int size;
private Entry<K, V>[] table;
// 1. 實現構造方法
public MyHashMap() {
table = new Entry[length];
}
public V put(K key, V value) {
int hash = key.hashCode();
int index = hash % length;
for (Entry<K, V> entry = table[index]; entry != null; entry = entry.next) {
// 如果存在相同的 key 直接將原先的覆蓋
if (entry.key.equals(key)) {
V oldValue = entry.value;
entry.value = value;
return oldValue;
}
entry = entry.next;
}
// 結點中並沒有
addEntry(key, value, index);
return value;
}
private void addEntry(K key, V value, int index) {
size++;
table[index] = new Entry<>(key, value, table[index]);
}
public int size() {
return size;
}
public V get(K key) {
int hash = key.hashCode();
int index = hash % length;
if (table[index] == null) {
return null;
}
for (Entry<K, V> entry = table[index]; entry != null; entry = entry.next) {
if (entry.key.equals(key)) {
return entry.value;
}
}
return null;
}
public void clear() {
// 不爲空則全部賦給 null, 將 size 修改爲 o
Entry<K, V>[] newTable;
if ((newTable = table) != null && size > 0) {
size = 0;
for (int i = 0; i < newTable.length; ++i) {
newTable[i] = null;
}
}
}
class Entry<K, V>{
private K key;
private V value;
private Entry<K, V> next;
public Entry(K key, V value, Entry<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
}
}
你知道 HashMap 的工作原理嗎?
這個題目相對比較寬泛,怎麼回答都可以。主要考察的是個人對於 HashMap 掌握的深度。
HashMap 中能 PUT 兩個相同的 key 嗎?爲什麼能或爲什麼不能?
可以,put 兩個相同的 key 的時候,會將之前 put 的值替換掉。
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
HashMap 中的鍵值可以爲 NULL?能簡單說一下原理嗎?
key 是可以爲 null 的,HashMap 插入 值時會先計算 key 的 hash 值,我們可以看到當 key 爲 null 時會返回 0, 也就是說位於數組的下標 0 處。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashMap 擴容機制是怎麼樣的, JDK 7與 JDK 8 有什麼不同嗎?
這個問題我也理解的不是很清楚,有興趣的可以好好研究一下給出的參考博客。
參考鏈接:
Java 8系列之重新認識HashMap