不管是數組仍是鏈表,其對數據的查詢表現都比較無力,要想知道一個元素是否在數組或鏈表中,只能從前向後挨個對比。出現這個問題的根源在於,咱們沒有辦法直接根據一個元素找到它存儲的位置,那有沒有辦法消除這個對比的過程呢?git
哈希表就是解決查詢問題的一種方案。在後續將會分析的二叉排序樹中,還會將數據排序以進行二分查找,將時間複雜度從O(n)下降到O(lg n)。github
通俗來說,哈希表就是經過關鍵字來獲取數據的一種數據結構,它經過把關鍵字映射爲表中的位置來獲取元素,這種映射主要是使用Hash函數。編程
Hash函數,其實是創建起key值與int值映射關係的函數。這就比如咱們每一個人都有一個身份證號同樣,不管是男是女,出生在何處,均可以經過身份證號來分辨,這就是把人的信息映射成一串數字的典型作法。Hash函數和此相似,不過是把任意的Java對象,映射成一個int數值,供哈希表使用。數組
而哈希表,就是一個數組,只是其元素不是按照數組的規則排列的。任何一個元素要放進哈希表中,都必須先經過Hash函數獲取到一個int數值,這個數值通過處理後將做爲它的存放位置,而後這個元素才能放進哈希表中。微信
能夠發現,數組與哈希表的操做不一樣之處主要在於,前者是直接插入,後者須要經過Hash函數計算後再插入。能夠經過下圖對比來理解:數據結構
哈希表徹底繼承了數組的優勢,又顯著的提升了查詢的速度,經過Hash函數使得查詢速度達到了O(1)。既然有了哈希表,它這麼優秀,爲什麼還須要數組的存在呢?那是由於Hash表是有缺陷的,這個缺陷就是哈希碰撞。函數
Hash函數所作的事,就是不管什麼對象,都根據一個規則映射爲一個int值。被轉換的對象有無數種可能,可是int的值是有限的,它只有2^32^個,這樣一來,必然會有不一樣的對象,映射獲得相同的int值,這就是所謂的哈希碰撞。發生碰撞以後,就要把不一樣的元素插入到相同的位置,這時候單純的使用一維數組已經沒法知足需求了。源碼分析
要解決哈希碰撞,咱們能夠想到多種解決方案。例如使用二維數組,將碰撞的元素按順序存儲起來,相似下圖:post
這樣的方式有一個很大的詬病,由於數組大小是固定的,因此第二維的數組長度都是同樣的,可是哈希碰撞必定是比較少發生的狀況,也就是咱們聲明瞭一個很大的數組,可是其中大部分都是閒置的,這就浪費了大量的內存。性能
還有一些方案是考慮了哈希表的散列化,將元素插入到空閒的位置去。由於哈希表基本不會像數組同樣每一個位置都有元素,這樣就能夠將碰撞的元素插入到這些空閒的位置中區,這種方案稱爲定址法。可是這個方法在擴展性上表現不佳,咱們這裏就再也不浪費篇幅來解釋它了。
目前比較通用的方法,就是使用數組+鏈表組合的方式。當出現哈希碰撞時,在該位置的數據就經過鏈表的方式連接起來,以下圖所示:
這是當前比較理想的方法,既繼承了數組的優勢,又在碰撞時繼承了鏈表的優勢,這也是哈希表強大的地方之一。
在JDK1.7及以前的版本中,HashMap
的存儲結構和上圖是一致的,在JDK1.8以後還加入了紅黑樹以進一步優化,在後續文章中咱們會對其進行詳盡的分析。
哈希表是一種優化存儲的思想,具體存儲元素的依然是其餘的數據結構。設計良好的哈希表,能同時兼備數組和鏈表的優勢,它能在插入和查找時都具有良好的性能。然而設計很差的哈希表,有可能會出現較多的哈希碰撞,致使鏈表過長,從而哈希表會更像一個鏈表。還有當數據量很大時,爲防止鏈表過長,就須要對數組進行擴容,這時就涉及到了數組的拷貝,其對性能的影響也很嚴重,因此須要提早對可能的狀況有良好的預測,才能真正發揮哈希表的優點。
本文到此就結束了,若是您喜歡個人文章,能夠關注個人微信公衆號:大大紙飛機
或者掃描下方二維碼直接添加:
您也能夠關注個人github:https://github.com/LtLei/articles
編程之路,道阻且長。惟,路漫漫其修遠兮,吾將上下而求索。