散列表也叫做哈希表(hash table),這種數據結構提供了鍵(key)和值(value)的映射關系。只要給出一個key,就可以高效查找它匹配的value,時間復雜度接近O(1);
哈希函數
哈希函數通過某種方式,把key和數組下標進行轉換。
在java中,每個對象都有屬于自己的hashcode,這個hashcode是區分不同對象的重要標識。無論對象自身的類型是什么,他們的hashcode都是一個整型變量。
最簡單的轉化方式是按照數組長度進行取模運算:
index = HashCode(Key)%Array.length
JDK中的哈希函數并沒有直接采取取模運算,而是利用位運算的方式來優化性能。
通過哈希函數,我們可以把字符串或其他類型的Key,轉化成數組的下標index
例如長度為8的數組
key = 001121,
index = HashCode(“001121”)%Array.length=1420036703%8=7
散列表的讀寫擴容操作
1、寫操作:在散列表中插入新的鍵值對
例如調用:
hashMap.put("002931","王五");
具體實現方式:
1、通過哈希函數,將key轉換成數組下標,例如5
2、如果數組小標5對應的位置沒有元素,就把這個鍵值對填充到數組下標為5的位置
但是,由于數組的長度是有限的,當插入的鍵值對越來越多,不同的key通過哈希函數獲得的下標可能是相同的
這種情況叫做哈希沖突
例如:
002936的對應數組下標為2,;
002947的對應數組下標也是2;
哈希沖突是無法避免的。解決哈希沖突的方法:
1、開放尋址法
例如:第6組鍵值對通過哈希函數得到下標2,該下標在數組中已經有了其他元素,那么就向后移動1位,觀察數組下標3是否有空
如果下標3也被占用,那么就再向后移動,直到找到空的位置。
(尋址的方式有很多,并不一定只是簡單地尋找當前元素的后一個元素,這里只是簡單舉例)
2、鏈表法
HashMap數組的每個元素不僅是一個鍵值對對象,還是一個鏈表的頭結點。每一個鍵值對對象通過next指針指向它的下一個鍵值對結點。當新來的鍵值對映射到與之沖突的數組位置時,只需要插入對應的鏈表中即可:
如下圖:
2、讀操作:通過給定的key,在散列表中查找對應的value
例如:調用函數hashMap.get(“002936”)
具體步驟:(以鏈表法為例)
1、通過哈希函數,將key轉換成數組下標,例如2
2、找到數組下標2對應的元素,如果這個元素的key是002936,那么就找到了
如果這個key不是002936,由于數組的每個元素都與一個鏈表對應,我們可以順著鏈表慢慢往下找,看看能否找到與key相匹配的結點
3、擴容操作:
當經過多次元素插入,散列表達到一定的飽和度時,key映射位置發生沖突的概率會逐漸提高。這樣一來,大量元素擁擠在相同的數組下標位置形成很長的鏈表,對后續的插入和查詢操作都會有很大影響。
此時就需要進行擴容:
影響擴容的因素:
1、HashMap當前長度
2、HashMap的負載因子,默認值為0.75f(在JDK中)
衡量HashMap需要進行擴容的操作如下:
HashMap.Size>=Capacity x LoadFactor
擴容具體步驟:
1、擴容,創建一個新的鍵值對空數組,長度是原數組的兩倍
2、重新Hash,遍歷原來的鍵值對數組,把所有的鍵值對重新Hash到新數組中(因為數組長度變化后,hash的規則也發生變化了)
經過擴容后,散列表重新變得稀疏。