散列表的原理和hash函數、解決hash衝突的方法,裝填因子、hash性能、應用場景
哈希表(散列表)查找定義
想象一個場景,如果想在一個學校中找出一個叫王五的學生,一般思路是去學生處把全校的學生名單列表拿出一個,一個一個的查找,這種方法就是普通的順序查找,依賴的是姓名關鍵字的比較。如果你恰巧遇見了一個王五班裏的同學張三,他就直接可以帶你去找到王五同學,這樣就不需要去遍歷比較姓名,就可以直接找到王五。
也就是說,只需要通過某個函數f(張三),使得
存儲位置(王五) = f(關鍵字)
那樣我們可以查找關鍵字f不需要比較就可以獲得需要記錄的位置,這種存儲技術就叫做散列技術。
散列技術是在記錄存儲位置和它的關鍵字之間建立一個確定的對應關係f,使得每個關鍵字key對應一個存儲位置f(key)。我們把這種對應關係f稱爲散列函數,又稱爲哈希(Hash)函數。採用散列技術將基礎存儲在一塊連續的存儲空間中,這塊連續的存儲空間稱爲散列表或哈希表(HashTable)。
哈希表實現步驟
整個過程其實只有兩步:
- 存儲時,通過哈希函數計算記錄的哈希地址,並按此地址存儲該記錄。
- 查找記錄時,同樣通過哈希函數計算記錄的散列地址,按此散列地址訪問該記錄。
所以說散列技術既是一種存儲方法,也是一種查找方法。它與線性表、樹、圖等數據結構不同的是,前面幾種結構,數據元素之間都存在某種邏輯關係,而哈希技術之間數據元素不存在邏輯關係,它只與關鍵字有關係。因此,哈希主要是面向查找的存儲結構。
哈希表性能分析
在沒有哈希衝突的情況下,哈希表是在查找中效率最高的,因爲它的時間複雜度爲O(1)。然而在實際應用中,衝突是不可避免的,那麼散列表的平均查找長度取決於那些因素呢?
- 處理衝突的方法:相同的關鍵字,相同的散列函數,處理衝突的方法不同,會使得平均查找長度不同。
- 哈希表的裝填因子α: α=填入表中的記錄個數/哈希表長度。α標誌着哈希表的裝滿程度。當填入表中的記錄越多,α越大,產生衝突的可能性就越大。也就是說哈希表的平均查找長度取決於裝填因子,而不是取決於集合中的記錄個數。可以通過將哈希表的空間設置的比查找集合大,通過犧牲空間,在換取查找效率。這樣我們的哈希查找的時間複雜度就是真的是O(1)了。
哈希表的應用場景
哈希表適用於那種查找性能要求高,數據元素之間無邏輯關係要求的情況。
1. 校驗安裝文件的完整性
在軟件部署的時候,計算軟件包當前的哈希值是否與預設值相等,防止軟件包被篡改或被替換。Linux提供了基於sha算法的命令,用於計算文件的哈希值
sha256sum fileName
2. 存儲和校驗用戶口令
用戶口令不能用明文存儲,更進一步,如果系統不知道用戶口令明文,那就更好了,而哈希算法就可以做到既不知道用戶明文,又可以校驗用戶口令。詳見《基於哈希算法的web賬戶口令存儲方法》,http://www.cnblogs.com/todsong/archive/2012/04/22/2465178.html
3. 校驗重複提交的消息
用戶可能因爲誤操作重複提交數據,而這些數據會對系統產生影響,若要拒絕這些消息,最好的方法就是在每次提交時,計算消息的哈希值,當發現疑似重複提交的時候,做消息哈希值的對比。這是一個CPU密集型的操作,如果系統的CPU負載比較低,可以考慮使用。至於如何在代碼中使用哈希算法,這裏就不描述了,Java、C++都有現成的算法庫可用。
4. 作爲數據庫樂觀鎖的條件
數據庫中,最常用的樂觀鎖方法是在表中增加額外的一列,用於記錄一行數據的版本值,通常是一個計數或是時間戳。但是,一張已經存在大量數據的表需要增加額外的版本列,似乎不太可行,也不太方便,此時可以通過哈希計算出虛擬的版本列,用於樂觀鎖定控制。Oracle數據庫提供了哈希算法的存儲過程,輸入某幾個列數據的字符連接,輸出該條記錄的哈希值,通過比較該值判斷數據是否被修改。下面是摘自《Oracle 9i&10g 編程藝術》的例子
5. 作爲數據庫表分區的分區條件
如果難以按照某一個列對數據庫表做分區,表中的數據又沒有太多的業務邏輯,那麼通過哈希函數強行分區是個不錯的選擇。詳見《Oracle分區表,哈希分區的新建與增加》,http://www.cnblogs.com/todsong/archive/2012/08/26/2657158.htm