2018-06-18 HashMap介紹,HashTable和HashMap的區(qū)別烹玉?

1驰怎、HashMap介紹

HashMap是基于哈希表實(shí)現(xiàn)的,每一個(gè)元素是一個(gè)key-value對(duì)二打,其內(nèi)部通過(guò)單鏈表解決沖突問(wèn)題县忌,容量不足(超過(guò)了閥值)時(shí),同樣會(huì)自動(dòng)增長(zhǎng)继效。

HashMap是非線程安全的症杏,只是用于單線程環(huán)境下,多線程環(huán)境下可以采用concurrent并發(fā)包下的concurrentHashMap瑞信。

HashMap 實(shí)現(xiàn)了Serializable接口厉颤,因此它支持序列化,實(shí)現(xiàn)了Cloneable接口凡简,能被克隆逼友。

HashMap存數(shù)據(jù)的過(guò)程是:

HashMap內(nèi)部維護(hù)了一個(gè)存儲(chǔ)數(shù)據(jù)的Entry數(shù)組,HashMap采用鏈表解決沖突秤涩,每一個(gè)Entry本質(zhì)上是一個(gè)單向鏈表帜乞。當(dāng)準(zhǔn)備添加一個(gè)key-value對(duì)時(shí),首先通過(guò)hash(key)方法計(jì)算hash值筐眷,然后通過(guò)indexFor(hash,length)求該key-value對(duì)的存儲(chǔ)位置黎烈,計(jì)算方法是先用hash&0x7FFFFFFF后,再對(duì)length取模匀谣,這就保證每一個(gè)key-value對(duì)都能存入HashMap中照棋,當(dāng)計(jì)算出的位置相同時(shí),由于存入位置是一個(gè)鏈表武翎,則把這個(gè)key-value對(duì)插入鏈表頭必怜。

HashMap中key和value都允許為null。key為null的鍵值對(duì)永遠(yuǎn)都放在以table[0]為頭結(jié)點(diǎn)的鏈表中后频。

HashMap的存儲(chǔ)結(jié)構(gòu)

圖中梳庆,紫色部分即代表哈希表,也稱為哈希數(shù)組卑惜,數(shù)組的每個(gè)元素都是一個(gè)單鏈表的頭節(jié)點(diǎn)膏执,鏈表是用來(lái)解決沖突的,如果不同的key映射到了數(shù)組的同一位置處露久,就將其放入單鏈表中更米。

HashMap內(nèi)存儲(chǔ)數(shù)據(jù)的Entry數(shù)組默認(rèn)是16,如果沒(méi)有對(duì)Entry擴(kuò)容機(jī)制的話毫痕,當(dāng)存儲(chǔ)的數(shù)據(jù)一多征峦,Entry內(nèi)部的鏈表會(huì)很長(zhǎng)迟几,這就失去了HashMap的存儲(chǔ)意義了。所以HasnMap內(nèi)部有自己的擴(kuò)容機(jī)制栏笆。HashMap內(nèi)部有:

變量size类腮,它記錄HashMap的底層數(shù)組中已用槽的數(shù)量;

變量threshold蛉加,它是HashMap的閾值蚜枢,用于判斷是否需要調(diào)整HashMap的容量(threshold = 容量*加載因子)

變量DEFAULT_LOAD_FACTOR = 0.75f,默認(rèn)加載因子為0.75

HashMap擴(kuò)容的條件是:當(dāng)size大于threshold時(shí)针饥,對(duì)HashMap進(jìn)行擴(kuò)容

擴(kuò)容是是新建了一個(gè)HashMap的底層數(shù)組厂抽,而后調(diào)用transfer方法,將就HashMap的全部元素添加到新的HashMap中(要重新計(jì)算元素在新的數(shù)組中的索引位置)丁眼。 很明顯筷凤,擴(kuò)容是一個(gè)相當(dāng)耗時(shí)的操作,因?yàn)樗枰匦掠?jì)算這些元素在新的數(shù)組中的位置并進(jìn)行復(fù)制處理苞七。因此嵌施,我們?cè)谟肏ashMap的時(shí),最好能提前預(yù)估下HashMap中元素的個(gè)數(shù)莽鸭,這樣有助于提高HashMap的性能吗伤。

HashMap共有四個(gè)構(gòu)造方法。構(gòu)造方法中提到了兩個(gè)很重要的參數(shù)初始容量和加載因子硫眨。這兩個(gè)參數(shù)是影響HashMap性能的重要參數(shù)足淆,其中容量表示哈希表中槽的數(shù)量(即哈希數(shù)組的長(zhǎng)度),初始容量是創(chuàng)建哈希表時(shí)的容量(從構(gòu)造函數(shù)中可以看出礁阁,如果不指明巧号,則默認(rèn)為16),加載因子是哈希表在其容量自動(dòng)增加之前可以達(dá)到多滿的一種尺度姥闭,當(dāng)哈希表中的條目數(shù)超出了加載因子與當(dāng)前容量的乘積時(shí)丹鸿,則要對(duì)該哈希表進(jìn)行 resize 操作(即擴(kuò)容)。

下面說(shuō)下加載因子棚品,如果加載因子越大靠欢,對(duì)空間的利用更充分,但是查找效率會(huì)降低(鏈表長(zhǎng)度會(huì)越來(lái)越長(zhǎng))铜跑;如果加載因子太小门怪,那么表中的數(shù)據(jù)將過(guò)于稀疏(很多空間還沒(méi)用,就開(kāi)始擴(kuò)容了)锅纺,對(duì)空間造成嚴(yán)重浪費(fèi)掷空。如果我們?cè)跇?gòu)造方法中不指定,則系統(tǒng)默認(rèn)加載因子為0.75,這是一個(gè)比較理想的值坦弟,一般情況下我們是無(wú)需修改的护锤。

另外,無(wú)論我們指定的容量為多少酿傍,構(gòu)造方法都會(huì)將實(shí)際容量設(shè)為不小于指定容量的2的次方的一個(gè)數(shù)烙懦,且最大值不能超過(guò)2的30次方。

HashMap源碼剖析:http://blog.csdn.net/ns_code/article/details/36034955

2拧粪、Hashtable簡(jiǎn)介

Hashtable同樣是基于哈希表實(shí)現(xiàn)的修陡,同樣每個(gè)元素是一個(gè)key-value對(duì),其內(nèi)部也是通過(guò)單鏈表解決沖突問(wèn)題,容量不足(超過(guò)了閥值)時(shí)闰非,同樣會(huì)自動(dòng)增長(zhǎng)俭茧。

Hashtable也是JDK1.0引入的類,是線程安全的碌尔,能用于多線程環(huán)境中。

Hashtable同樣實(shí)現(xiàn)了Serializable接口,它支持序列化旷余,實(shí)現(xiàn)了Cloneable接口,能被克隆扁达。

Hashtable和HashMap比較相似

Hashtable源碼剖析:http://blog.csdn.net/ns_code/article/details/36191279

3正卧、HashTable和HashMap區(qū)別
  • (1)繼承的父類不同

    Hashtable繼承自Dictionary類,而HashMap繼承自AbstractMap類跪解。但二者都實(shí)現(xiàn)了Map接口炉旷。

  • (2)線程安全性不同

javadoc中關(guān)于hashmap的一段描述如下:此實(shí)現(xiàn)不是同步的。如果多個(gè)線程同時(shí)訪問(wèn)一個(gè)哈希映射叉讥,而其中至少一個(gè)線程從結(jié)構(gòu)上修改了該映射窘行,則它必須保持外部同步。

Hashtable 中的方法是Synchronize的图仓,而HashMap中的方法在缺省情況下是非Synchronize的罐盔。在多線程并發(fā)的環(huán)境下,可以直接使用Hashtable救崔,不需要自己為它的方法實(shí)現(xiàn)同步惶看,但使用HashMap時(shí)就必須要自己增加同步處理。(結(jié)構(gòu)上的修改是指添加或刪除一個(gè)或多個(gè)映射關(guān)系的任何操作六孵;僅改變與實(shí)例已經(jīng)包含的鍵關(guān)聯(lián)的值不是結(jié)構(gòu)上的修改碳竟。)這一般通過(guò)對(duì)自然封裝該映射的對(duì)象進(jìn)行同步操作來(lái)完成。如果不存在這樣的對(duì)象狸臣,則應(yīng)該使用 Collections.synchronizedMap 方法來(lái)“包裝”該映射莹桅。最好在創(chuàng)建時(shí)完成這一操作,以防止對(duì)映射進(jìn)行意外的非同步訪問(wèn),如下所示:

Map m = Collections.synchronizedMap(new HashMap(...));

Hashtable 線程安全很好理解诈泼,因?yàn)樗總€(gè)方法中都加入了Synchronize懂拾。這里我們分析一下HashMap為什么是線程不安全的:

HashMap底層是一個(gè)Entry數(shù)組,當(dāng)發(fā)生hash沖突的時(shí)候铐达,hashmap是采用鏈表的方式來(lái)解決的岖赋,在對(duì)應(yīng)的數(shù)組位置存放鏈表的頭結(jié)點(diǎn)。對(duì)鏈表而言瓮孙,新加入的節(jié)點(diǎn)會(huì)從頭結(jié)點(diǎn)加入唐断。

  • (3)是否提供contains方法

HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey杭抠,因?yàn)閏ontains方法容易讓人引起誤解脸甘。

Hashtable則保留了contains,containsValue和containsKey三個(gè)方法偏灿,其中contains和containsValue功能相同丹诀。

Hashtable的ContainsKey方法和ContainsValue的源碼:

public boolean containsValue(Object value) {      
     return contains(value);      
 } 

// 判斷Hashtable是否包含“值(value)”      
public synchronized boolean contains(Object value) {      
     //注意,Hashtable中的value不能是null翁垂,      
     // 若是null的話铆遭,拋出異常!      
     if (value == null) {      
         throw new NullPointerException();      
     }      
    
     // 從后向前遍歷table數(shù)組中的元素(Entry)      
     // 對(duì)于每個(gè)Entry(單向鏈表),逐個(gè)遍歷沿猜,判斷節(jié)點(diǎn)的值是否等于value      
     Entry tab[] = table;      
     for (int i = tab.length ; i-- > 0 ;) {      
         for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {      
             if (e.value.equals(value)) {      
                 return true;      
             }      
         }      
     }      
     return false;      
 } 

// 判斷Hashtable是否包含key      
public synchronized boolean containsKey(Object key) {      
     Entry tab[] = table;      
     //計(jì)算hash值枚荣,直接用key的hashCode代替    
     int hash = key.hashCode();        
     // 計(jì)算在數(shù)組中的索引值     
     int index = (hash & 0x7FFFFFFF) % tab.length;      
     // 找到“key對(duì)應(yīng)的Entry(鏈表)”,然后在鏈表中找出“哈希值”和“鍵值”與key都相等的元素      
     for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {      
         if ((e.hash == hash) && e.key.equals(key)) {      
             return true;      
         }      
     }      
     return false;      
 }  

HashMap的ContainsKey方法和ContainsValue的源碼:

// HashMap是否包含key      
public boolean containsKey(Object key) {      
     return getEntry(key) != null;      
}  

// 返回“鍵為key”的鍵值對(duì)      
final Entry<K,V> getEntry(Object key) {      
     // 獲取哈希值      
     // HashMap將“key為null”的元素存儲(chǔ)在table[0]位置啼肩,“key不為null”的則調(diào)用hash()計(jì)算哈希值      
     int hash = (key == null) ? 0 : hash(key.hashCode());      
     // 在“該hash值對(duì)應(yīng)的鏈表”上查找“鍵值等于key”的元素      
     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 != null && key.equals(k))))      
                return e;      
      } 
      return null;      
}  

// 是否包含“值為value”的元素      
public boolean containsValue(Object value) {      
    // 若“value為null”橄妆,則調(diào)用containsNullValue()查找      
    if (value == null)      
            return containsNullValue();      
     
    // 若“value不為null”,則查找HashMap中是否有值為value的節(jié)點(diǎn)疟游。      
    Entry[] tab = table;      
        for (int i = 0; i < tab.length ; i++)      
            for (Entry e = tab[i] ; e != null ; e = e.next)      
                if (value.equals(e.value))      
                    return true;      
    return false;      
}  
  • (4)key和value是否允許null值

    其中key和value都是對(duì)象呼畸,并且不能包含重復(fù)key,但可以包含重復(fù)的value颁虐。

    通過(guò)上面的ContainsKey方法和ContainsValue的源碼我們可以很明顯的看出:

Hashtable中蛮原,key和value都不允許出現(xiàn)null值。但是如果在Hashtable中有類似put(null,null)的操作另绩,編譯同樣可以通過(guò)儒陨,因?yàn)閗ey和value都是Object類型,但運(yùn)行時(shí)會(huì)拋出NullPointerException異常笋籽,這是JDK的規(guī)范規(guī)定的蹦漠。
HashMap中,null可以作為鍵车海,這樣的鍵只有一個(gè)笛园;可以有一個(gè)或多個(gè)鍵所對(duì)應(yīng)的值為null。當(dāng)get()方法返回null值時(shí),可能是 HashMap中沒(méi)有該鍵研铆,也可能使該鍵所對(duì)應(yīng)的值為null埋同。因此,在HashMap中不能由get()方法來(lái)判斷HashMap中是否存在某個(gè)鍵棵红, 而應(yīng)該用containsKey()方法來(lái)判斷凶赁。

  • (5)兩個(gè)遍歷方式的內(nèi)部實(shí)現(xiàn)上不同

Hashtable、HashMap都使用了 Iterator逆甜。而由于歷史原因虱肄,Hashtable還使用了Enumeration的方式 。

  • (6)hash值不同

哈希值的使用不同交煞,HashTable直接使用對(duì)象的hashCode咏窿。而HashMap重新計(jì)算hash值。

hashCode是jdk根據(jù)對(duì)象的地址或者字符串或者數(shù)字算出來(lái)的int類型的數(shù)值错敢。

Hashtable計(jì)算hash值翰灾,直接用key的hashCode()缕粹,而HashMap重新計(jì)算了key的hash值稚茅,Hashtable在求hash值對(duì)應(yīng)的位置索引時(shí),用取模運(yùn)算平斩,而HashMap在求位置索引時(shí)亚享,則用與運(yùn)算,且這里一般先用hash&0x7FFFFFFF后绘面,再對(duì)length取模欺税,&0x7FFFFFFF的目的是為了將負(fù)的hash值轉(zhuǎn)化為正值,因?yàn)閔ash值有可能為負(fù)數(shù)揭璃,而&0x7FFFFFFF后晚凿,只有符號(hào)外改變,而后面的位都不變瘦馍。

  • (7)內(nèi)部實(shí)現(xiàn)使用的數(shù)組初始化和擴(kuò)容方式不同

HashTable在不指定容量的情況下的默認(rèn)容量為11歼秽,而HashMap為16,Hashtable不要求底層數(shù)組的容量一定要為2的整數(shù)次冪情组,而HashMap則要求一定為2的整數(shù)次冪燥筷。

Hashtable擴(kuò)容時(shí),將容量變?yōu)樵瓉?lái)的2倍加1院崇,而HashMap擴(kuò)容時(shí)肆氓,將容量變?yōu)樵瓉?lái)的2倍。

Hashtable和HashMap它們兩個(gè)內(nèi)部實(shí)現(xiàn)方式的數(shù)組的初始大小和擴(kuò)容的方式底瓣。HashTable中hash數(shù)組默認(rèn)大小是11谢揪,增加的方式是 old*2+1。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拨扶,隨后出現(xiàn)的幾起案子寺滚,更是在濱河造成了極大的恐慌,老刑警劉巖屈雄,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件村视,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡酒奶,警方通過(guò)查閱死者的電腦和手機(jī)蚁孔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惋嚎,“玉大人杠氢,你說(shuō)我怎么就攤上這事×砦椋” “怎么了鼻百?”我有些...
    開(kāi)封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)摆尝。 經(jīng)常有香客問(wèn)我温艇,道長(zhǎng),這世上最難降的妖魔是什么堕汞? 我笑而不...
    開(kāi)封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任勺爱,我火速辦了婚禮,結(jié)果婚禮上讯检,老公的妹妹穿的比我還像新娘琐鲁。我一直安慰自己,他們只是感情好人灼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布围段。 她就那樣靜靜地躺著,像睡著了一般投放。 火紅的嫁衣襯著肌膚如雪奈泪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天跪呈,我揣著相機(jī)與錄音段磨,去河邊找鬼。 笑死耗绿,一個(gè)胖子當(dāng)著我的面吹牛苹支,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播误阻,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼债蜜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晴埂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起寻定,我...
    開(kāi)封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤儒洛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后狼速,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體琅锻,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年向胡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恼蓬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡僵芹,死狀恐怖处硬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拇派,我是刑警寧澤荷辕,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站件豌,受9級(jí)特大地震影響疮方,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苟径,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一案站、第九天 我趴在偏房一處隱蔽的房頂上張望躬审。 院中可真熱鬧棘街,春花似錦、人聲如沸承边。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)博助。三九已至险污,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間富岳,已是汗流浹背蛔糯。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窖式,地道東北人蚁飒。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像萝喘,于是被迫代替她去往敵國(guó)和親淮逻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子琼懊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容