HashMap毋庸置疑,一定是我們這些Java程式員第一大工具 + 生產力,因為其在儲存資料方面,有些“無所不能”哈,而且效率與效能都合我們的意。也是因為這個HashMap在應用程式中應用過多,所以網上出現了針對HashMap的各種剖析,呵呵,鄙人也看過其源碼!瞭解過其具體的實現,所以此時有意來總結一下在網路上的一些對HashMap的各種解析。
HashMap中的Hash演算法
HashMap使用了散列表,而散列表中要關注的問題是,如何儘可能地減少散列值的衝突,通常有兩種方法,鏈表法和開放地址法。(嗯!資料結構的書上會有更詳盡的解釋)
【鏈表法】將相同hash值的對象組織成為一個鏈表放在hash值對應的槽位;
【開放地址法】通過一個探索演算法,當某個槽位已經被佔據的情況下繼續尋找下一個可以被使用的槽位。
【負載因子和容量】:在JDK中,一個HashMap的實際容量就為 【因子】*【容量】,
JDK提供的預設值為:16* 0.75=12;
【負載因子】,負載因子a = 散列表的實際元素數目(n)/散列表的容量(m)
在此,負載因子衡量的是一個散列表的空間使用程度,負載因子越大表示散列表的裝填程度越高,反之越少。
在JDK中,HashMap採用的是鏈表法的方式,而鏈表為單向鏈表,在刪除過程中要自己維護previous節點
【SourceCode】
for (Entry 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;
}
HashMap的資料結構:數組和鏈表的結合體(被稱為“鏈表散列)。如所示:
從這張圖,可得到一些有用資訊,HashMap中get方法的高效率,因為在HashMap中,資料時儲存在數組上,從而就利用了資料的天生優點——可直接通過索引定位元素的位置。當然,在HashMap中的實現中,這個過程並不是像平常那樣直接使用,看看HashMap中get方法實現吧
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry 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;
}
在方法體中,真正定位元素的存放位置也是通過hash值的。
每個元素上的鏈表存放,利用put方法塞元素時,先根據key的hash值得到這個元素在數組上的位置(下標),然後就可以把這個元素放在對應的位置上,若是,這個位置上已有其他元素,那麼就會在同一個位置上將元素以鏈表的形式存放。新的加入放在鏈頭,最先加入的放在鏈尾。HashMap 採用一種所謂的“Hash 演算法”來決定每個元素的儲存位置
HashMap中的resize的實現
當hashmap中的元素越來越多的時候,碰撞的幾率也就越來越高(因為數組的長度是固定的),所以為了提高查詢的效率,就要對hashmap的數組進行擴容。在這個過程中,最消耗效能的關鍵點出現:原數組中的資料必須重新計算在新數組中的位置,並放進去。
這個問題,就要我們在開發過程中,要預知一下HashMap的可能大小,再給它初始化為離這個大小的接近2的整數次冪次方吧!但,這個並不是最完美的解決之道。看某位仁兄的:
比如說,我們有1000個元素new HashMap(1000), 但是理論上來講new HashMap( 1024)更合適,不過,即使是1000,hashmap也自動會將其設定為1024。 但是new HashMap(1024)還不是更合適的,因為0.75*1000 1000, 我們必須這樣new HashMap(2048)才最合適,既考慮了&的問題,也避免了resize的問題。
HashMap的有效使用
如果你想有效使用HashMap,你就必須重寫在其的HashCode()!
覆蓋hashCode方法,使相同內容的對象來說它們的hashcode也就相同了(這樣就能迅速的定位某個元素的位置)
覆蓋equals方法,為了在HashMap中判斷兩個key是否相等時使結果有意義。
在改寫equals方法的時候,需要滿足以下三點:
(1) 自反性:就是說a.equals(a)必須為true。
(2) 對稱性:就是說a.equals(b)=true的話,b.equals(a)也必須為true。
(3) 傳遞性:就是說a.equals(b)=true,並且b.equals(c)=true的話,a.equals(c)也必須為true。
兩條重寫建議牢記的原則:
"不為一原則",不必對每個不同的對象都產生一個唯一的hashcode,只要你的HashCode方法使get()能夠得到put()放進去的內容就可以了。
"分散原則",產生hashcode的演算法盡量使hashcode的值分散一些,不要很多hashcode都集中在一個範圍內,這樣有利於提高HashMap的效能。
【參考的原文章出處,非常感謝大夥們的好文】
http://www.javaeye.com/topic/368087
http://www.javaeye.com/topic/539465