標籤:java hashmap
HashMap的底層主要是基於數組和鏈表來實現的,它之所以有相當快的查詢速度主要是因為它是通過計算散列碼來決定儲存的位置。HashMap中主要是通過key的hashCode來計算hash值的,只要hashCode相同,計算出來的hash值就一樣。如果儲存的對象對多了,就有可能不同的對象所算出來的hash值是相同的,這就出現了所謂的hash衝突。學過資料結構的同學都知道,解決hash衝突的方法有很多,HashMap底層是通過鏈表來解決hash衝突的。 HashMap其實就是一個Entry數組,Entry對象中包含了鍵和值,其中next也是一個Entry對象,它就是用來處理hash衝突的,形成一個鏈表。數組的每個元素都是一個單鏈表的前端節點,鏈表是用來解決衝突的,如果不同的key映射到了數組的同一位置處,就將其放入單鏈表中。
Hashtable 與 HashMap類似,但是主要有6點不同。 1.HashTable的方法是同步的,HashMap未經同步,所以在多線程場合要手動同步HashMap這個區別就像Vector和ArrayList一樣。 2.HashTable不允許null值,key和value都不可以,HashMap允許null值,key和value都可以。HashMap允許key值只能由一個null值,因為hashmap如果key值相同,新的key, value將替代舊的。 3.HashTable有一個contains(Object value)功能和containsValue(Object value)功能一樣。 4.HashTable使用Enumeration,HashMap使用Iterator。 5.HashTable中hash數組預設大小是11,增加的方式是 old*2+1。HashMap中hash數組的預設大小是16,而且一定是2的指數。
1、HashTable的方法是同步的,HashMap未經同步 Hashtable 提供的幾個主要方法,包括 get(), put(), remove() 等。每個方法本身都是 synchronized 的,會對整個對象進行加鎖操作,不會出現兩個線程同時對資料進行操作的情況,因此保證了執行緒安全性,但是也大大的降低了執行效率。
2、HashMap中hash數組的預設大小是16,而且一定是2的指數
- static int indexFor(int h, int length) {
- return h & (length-1);
- }
將key的二次hash值,與長度減一進行與操作,這一步可謂經典,通常我們會用模數的方式來定位元組中的某個位置,我們首先想到的就是把hashcode對數組長度模數運算,這樣一來,元素的分布相對來說是比較均勻的。但是,“模”運算的消耗還是比較大的,能不能找一種更快速,消耗更小的方式那?hashMap用這種方法,而且length即capacity的值,面capacity又是2的倍數,減1之後,表示成二進位就全部是1了,那麼與全部為1的一個數進行與操作,速度會大大提升了。這就是為什麼"capacity的值是2的倍數"
3、HashMap解決衝突的辦法 1)再雜湊法 2)拉鏈發
- public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value);
- int hash = hash(key.hashCode());
- int i = indexFor(hash, table.length);
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
-
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
根據key的hash值得到這個元素在數組中的位置(即下標),然後就可以把這個元素放到對應的位置中了。如果這個元素所在的位子上已經存放有其他元素了,那麼在同一個位子上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。從hashmap中get元素時,首先計算key的hashcode,找到數組中對應位置的某一元素,然後通過key的equals方法在對應位置的鏈表中找到需要的元素。 調用int hash = hash(key.hashCode());這是hashmap的一個自訂的hash,在key.hashCode()基礎上進行二次hash。再雜湊法能夠解決開放地址法的聚集現象,但是卻增加了時間複雜度。
- static int hash(int h) {
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
- }
改進傳統的hash方法,而且盡量保證key的每一位都會影響到最後的hash值,以達到減少hash衝突的目的.
4、HashMap的建構函式
- if (initialCapacity < 0)
- throw new IllegalArgumentException("Illegal initial capacity: " +
- initialCapacity);
- if (initialCapacity > MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
- if (loadFactor <= 0 || Float.isNaN(loadFactor))
- throw new IllegalArgumentException("Illegal load factor: " +
- loadFactor);
- // Find a power of 2 >= initialCapacity
- int capacity = 1;
- while (capacity < initialCapacity)
- capacity <<= 1;
- this.loadFactor = loadFactor;
- threshold = (int)(capacity * loadFactor);
- table = new Entry[capacity];
- init();
- }
loadFactor :載入因子,載入因子與HashMap resize有關。預設為0.75 capacity:容器大小,預設值為16 其大小為上面所說的資料結構中數組的長度。 table:即為上面資料結構圖中,X方向的數組(transient Entry[] table;) threshold :resize的臨界值,即當HashMap中無素個數達到該值時,HashMap就會調用其resize方法,重新擴充大小。 while (capacity < initialCapacity) capacity <<= 1; 這段代碼說明什麼:capacity的值是2的倍數,即使設定初始值是1000,hashmap也自動會將其設定為1024。
5、雜湊表解決衝突的常用方法 1)開放地址法 2)拉鏈法 3)再雜湊法
拉鏈法的優點 ,與開放定址法相比,拉鏈法有如下幾個優點: ①拉鏈法處理衝突簡單,且無堆積現象,即非同義字決不會發生衝突,因此平均尋找長度較短;②由於拉鏈法中各鏈表上的結點空間是動態申請的,故它更適合於造表前無法確定表長的情況;③開放定址法為減少衝突,要求裝填因子α較小,故當結點規模較大時會浪費很多空間。而拉鏈法中可取α≥1,且結點較大時,拉鏈法中增加的指標域可忽略不計,因此節省空間的;④在用拉鏈法構造的散列表中,刪除結點的操作易於實現。只要簡單地刪去鏈表上相應的結點即可。而對開放地址法構造的散列表,刪除結點不能簡單地將被刪結 點的空間置為空白,否則將截斷在它之後填人散列表的同義字結點的尋找路徑。這是因為各種開放地址法中,空地址單元(即開放地址)都是尋找失敗的條件。因此在 用開放地址法處理衝突的散列表上執行刪除操作,只能在被刪結點上做刪除標記,而不能真正刪除結點。拉鏈法的缺點 拉鏈法的缺點是:指標需要額外的空間,故當結點規模較小時,開放定址法較為節省空間的,而若將節省的指
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
HashMap源碼學習筆記