剛出來工作的時候,老大要我從文本提取一些數據, 篩選排序什麼的,那時我只曉得用ArrayList, 結果事情是做完了,執行卻有點慢。老大看代碼說我是在寫C語言, 讓我試試用HashMap處理, 結果是哇,快好多,從此對HashMap很有好感。年復一年的面試, HashTable,HashMap,TreeMap有啥區別啊,爲什麼那麼快啊之類, 每個工程師都會在面試中得到鍛鍊和成長:p。面試受打擊了,可能網上一搜這個面試題,喔原來答案是這個。真去看下源碼的人估計不多,老實說筆者最近才仔細拜讀-_-,結合搜索的分享的文章,結果發現自己的源碼太新了:p JDK1.8的,大多文章還是JDK1.7的,突然想起看過一篇文章關於JDK重寫了HashMap爲了解決哈希碰撞的事。JDK能對已有老的庫能升級也是蠻罕見的事,所以就有了這篇文章。
- 先看JDK1.7的HashMap爲什麼快?
HashMap使用Entry<K,V>[] table保存數據, 每個節點Entry.next指向另外Entry節點, next存在則表示有相同的key哈希, JDK1.7使用鏈表保存解決衝突。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
HashMap查找快關鍵的因素是key hashcode在table數組有一個對應的下標。indexFor是精心設計過的,table.length必須爲2的整數次冪, 即length-1爲奇數, 奇數做位與纔可能產生奇數或偶數(偶數位與就都是偶數了), 保證Entry在table數組中的均勻分佈。
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[<span style="color:#FF0000;">indexFor</span>(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;
}
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
//<span style="color:#FF0000;"> assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"</span>;
return <span style="color:#FF0000;">h & (length-1)</span>;
}
話說redis,php哈希採用了類似的數據結構, 可能redis的哈希算法可能衝突會小些。
那哈希衝突會帶來什麼問題呢?假設我們寫個POJO, hashCode()總是返回0,這個POJO作爲HashMap key值的就都衝突了,所有的Entry就退化爲鏈表了,查找就是鏈表的線性效率了,有人惡意造數據造成了衝突,系統查找響應就很慢, 導致DOS拒絕服務。所以JDK1.8就稍微優化了下。
- JDK1.8 HashMap改進
當哈希衝突的鏈表達到一定長度(好像是8),就改用紅黑樹解決保存節點,就算衝突多查找也不會太慢。
虎頭蛇尾。。。下班了。。see you very soon。
Cheers