HashMap是基於哈希表實現的,每個元素是一個key-value對,其內部經過單鏈表解決衝突問題,容量不足(超過了閥值)時,一樣會自動增加。html
HashMap是非線程安全的,只是用於單線程環境下,多線程環境下能夠採用concurrent併發包下的concurrentHashMap。java
HashMap 實現了Serializable接口,所以它支持序列化,實現了Cloneable接口,能被克隆。數組
HashMap存數據的過程是:安全
HashMap內部維護了一個存儲數據的Entry數組,HashMap採用鏈表解決衝突,每個Entry本質上是一個單向鏈表。當準備添加一個key-value對時,首先經過hash(key)方法計算hash值,而後經過indexFor(hash,length)求該key-value對的存儲位置,計算方法是先用hash&0x7FFFFFFF後,再對length取模,這就保證每個key-value對都能存入HashMap中,當計算出的位置相同時,因爲存入位置是一個鏈表,則把這個key-value對插入鏈表頭。多線程
HashMap中key和value都容許爲null。key爲null的鍵值對永遠都放在以table[0]爲頭結點的鏈表中。併發
瞭解了數據的存儲,那麼數據的讀取也就很容易就明白了。app
HashMap的存儲結構,以下圖所示:函數
圖中,紫色部分即表明哈希表,也稱爲哈希數組,數組的每一個元素都是一個單鏈表的頭節點,鏈表是用來解決衝突的,若是不一樣的key映射到了數組的同一位置處,就將其放入單鏈表中。oop
HashMap內存儲數據的Entry數組默認是16,若是沒有對Entry擴容機制的話,當存儲的數據一多,Entry內部的鏈表會很長,這就失去了HashMap的存儲意義了。因此HasnMap內部有本身的擴容機制。HashMap內部有:post
變量size,它記錄HashMap的底層數組中已用槽的數量;
變量threshold,它是HashMap的閾值,用於判斷是否須要調整HashMap的容量(threshold = 容量*加載因子)
變量DEFAULT_LOAD_FACTOR = 0.75f,默認加載因子爲0.75
HashMap擴容的條件是:當size大於threshold時,對HashMap進行擴容
擴容是是新建了一個HashMap的底層數組,然後調用transfer方法,將就HashMap的所有元素添加到新的HashMap中(要從新計算元素在新的數組中的索引位置)。 很明顯,擴容是一個至關耗時的操做,由於它須要從新計算這些元素在新的數組中的位置並進行復制處理。所以,咱們在用HashMap的時,最好能提早預估下HashMap中元素的個數,這樣有助於提升HashMap的性能。
HashMap共有四個構造方法。構造方法中提到了兩個很重要的參數:初始容量和加載因子。這兩個參數是影響HashMap性能的重要參數,其中容量表示哈希表中槽的數量(即哈希數組的長度),初始容量是建立哈希表時的容量(從構造函數中能夠看出,若是不指明,則默認爲16),加載因子是哈希表在其容量自動增長以前能夠達到多滿的一種尺度,當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 resize 操做(即擴容)。
下面說下加載因子,若是加載因子越大,對空間的利用更充分,可是查找效率會下降(鏈表長度會愈來愈長);若是加載因子過小,那麼表中的數據將過於稀疏(不少空間還沒用,就開始擴容了),對空間形成嚴重浪費。若是咱們在構造方法中不指定,則系統默認加載因子爲0.75,這是一個比較理想的值,通常狀況下咱們是無需修改的。
另外,不管咱們指定的容量爲多少,構造方法都會將實際容量設爲不小於指定容量的2的次方的一個數,且最大值不能超過2的30次方
對HashMap想進一步深刻了解的朋友推薦看一下HashMap源碼剖析:http://blog.csdn.net/ns_code/article/details/36034955
Hashtable一樣是基於哈希表實現的,一樣每一個元素是一個key-value對,其內部也是經過單鏈表解決衝突問題,容量不足(超過了閥值)時,一樣會自動增加。
Hashtable也是JDK1.0引入的類,是線程安全的,能用於多線程環境中。
Hashtable一樣實現了Serializable接口,它支持序列化,實現了Cloneable接口,能被克隆。
Hashtable和HashMap比較類似,感興趣的朋友能夠看「Hashtable源碼剖析」這篇博客:http://blog.csdn.net/ns_code/article/details/36191279
下面主要介紹一下HashTable和HashMap區別
Hashtable繼承自Dictionary類,而HashMap繼承自AbstractMap類。但兩者都實現了Map接口。
Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省狀況下是非Synchronize的。在多線程併發的環境下,能夠直接使用Hashtable,不須要本身爲它的方法實現同步,但使用HashMap時就必需要本身增長同步處理。(結構上的修改是指添加或刪除一個或多個映射關係的任何操做;僅改變與實例已經包含的鍵關聯的值不是結構上的修改。)這通常經過對天然封裝該映射的對象進行同步操做來完成。若是不存在這樣的對象,則應該使用 Collections.synchronizedMap 方法來「包裝」該映射。最好在建立時完成這一操做,以防止對映射進行意外的非同步訪問,以下所示:
Map m = Collections.synchronizedMap(new HashMap(...));
Hashtable 線程安全很好理解,由於它每一個方法中都加入了Synchronize。這裏咱們分析一下HashMap爲何是線程不安全的:
HashMap底層是一個Entry數組,當發生hash衝突的時候,hashmap是採用鏈表的方式來解決的,在對應的數組位置存放鏈表的頭結點。對鏈表而言,新加入的節點會從頭結點加入。
咱們來分析一下多線程訪問:
(1)在hashmap作put操做的時候會調用下面方法:
在hashmap作put操做的時候會調用到以上的方法。如今假如A線程和B線程同時對同一個數組位置調用addEntry,兩個線程會同時獲得如今的頭結點,而後A寫入新的頭結點以後,B也寫入新的頭結點,那B的寫入操做就會覆蓋A的寫入操做形成A的寫入操做丟失
( 2)刪除鍵值對的代碼
當多個線程同時操做同一個數組位置的時候,也都會先取得如今狀態下該位置存儲的頭結點,而後各自去進行計算操做,以後再把結果寫會到該數組位置去,其實寫回的時候可能其餘的線程已經就把這個位置給修改過了,就會覆蓋其餘線程的修改
(3)addEntry中當加入新的鍵值對後鍵值對總數量超過門限值的時候會調用一個resize操做,代碼以下:
這個操做會新生成一個新的容量的數組,而後對原數組的全部鍵值對從新進行計算和寫入新的數組,以後指向新生成的數組。
當多個線程同時檢測到總數量超過門限值的時候就會同時調用resize操做,各自生成新的數組並rehash後賦給該map底層的數組table,結果最終只有最後一個線程生成的新數組被賦給table變量,其餘線程的均會丟失。並且當某些線程已經完成賦值而其餘線程剛開始的時候,就會用已經被賦值的table做爲原始數組,這樣也會有問題。
HashMap把Hashtable的contains方法去掉了,改爲containsValue和containsKey,由於contains方法容易讓人引發誤解。
Hashtable則保留了contains,containsValue和containsKey三個方法,其中contains和containsValue功能相同。
咱們看一下Hashtable的ContainsKey方法和ContainsValue的源碼:
下面咱們看一下HashMap的ContainsKey方法和ContainsValue的源碼:
經過上面源碼的比較,咱們能夠獲得第四個不一樣的地方
其中key和value都是對象,而且不能包含重複key,但能夠包含重複的value。
經過上面的ContainsKey方法和ContainsValue的源碼咱們能夠很明顯的看出:
Hashtable中,key和value都不容許出現null值。可是若是在Hashtable中有相似put(null,null)的操做,編譯一樣能夠經過,由於key和value都是Object類型,但運行時會拋出NullPointerException異常,這是JDK的規範規定的。Hashtable、HashMap都使用了 Iterator。而因爲歷史緣由,Hashtable還使用了Enumeration的方式 。
哈希值的使用不一樣,HashTable直接使用對象的hashCode。而HashMap從新計算hash值。
hashCode是jdk根據對象的地址或者字符串或者數字算出來的int類型的數值。
Hashtable計算hash值,直接用key的hashCode(),而HashMap從新計算了key的hash值,Hashtable在求hash值對應的位置索引時,用取模運算,而HashMap在求位置索引時,則用與運算,且這裏通常先用hash&0x7FFFFFFF後,再對length取模,&0x7FFFFFFF的目的是爲了將負的hash值轉化爲正值,由於hash值有可能爲負數,而&0x7FFFFFFF後,只有符號外改變,然後面的位都不變。
Hashtable和HashMap它們兩個內部實現方式的數組的初始大小和擴容的方式。HashTable中hash數組默認大小是11,增長的方式是 old*2+1。