Python 中的哈希表


Python 中的哈希表:對字典的理解

  有沒有想過,Python中的字典為什么這么高效穩定。原因是他是建立在hash表上。了解Python中的hash表有助於更好的理解Python,因為Python中字典無處不在。

hash 函數


  哈希函數是一個可以將任意長度的數據塊映射到固定長度的值,這個步驟稱為hash,也就是散列。

hash 函數有三個主要的特征:

  1. 計算迅速:計算一個數據塊的hash值非常快
  2. 確定性:相同用字符串會產生相同的hash值
  3. 結果固定長度:不管輸入的是,一個字節還是十個字節,或者上萬個字節,結果總是預先確定的長度。

  另一個特征在hash函數中非常普遍,即他們是單方向的:通過函數實現后,原始數據丟失了,我們可以通過字符串得到一個hash值,但不能通過一個hash也就是散列得到原始的字符串(因為有對數據降維的方法會造成數據的丟失)。這種特性並不是對所有hash函數的強制性規定,但是當需要加密安全時,這種性質還是挺好用的。

一些比較受歡迎的算法包括:MD5、SHA-1、SHA-2,NTLM.

關於hash的一些使用方法


  很多東西都依賴hash,hash表就是其中一例,另一些用法是出於加密和的原因

   一個具體的例子就是當你嘗試從網上下載開源軟件時。通常你都會發現一個關聯文件,這個關聯文件就是這個文件簽名。這個簽名僅僅是源文件的散列值它非常有用,你為你可以自己通過計算下載好的文件的散列值並與網站上提供的簽名進行對比,這樣就可以確認你自己下載的文件沒有損壞。

  另一種用法是存儲用戶的密碼。你有沒有問過你自己,當你忘掉了一個網站的登錄密碼的時候,你還想從這個網站上恢復登錄,它只會讓你重新確定一個新的登錄密碼而不是給你原來你選擇的密碼,這是因為網站並不會儲存你完整的密碼,而是你密碼的hash值。

  這么做是出於安全因素,因為如果黑客搞到了數據庫的權限,他們不會知道你真實的密碼,而是僅僅得到你密碼的哈希值,又因為哈希函數是單向的,他們根本不可能從哈希值得到你的密碼

Python 里的hash()函數


  Python 中含有內置的函數去給對象生成哈希值,也就是hash()函數,這個函數將一個對象作為輸入,返回一個整數的hash值。

  內部的,這個函數涉及到.\__hash\__()這個輸入對象的方法,所以呢,如果你想使你的自定義類可哈希化,你需要做的是實現.\__hash__()這個方法返回出一個整數,這個整數是基於你的對象的內部狀態決定的。

先開始一些基本的小例子,先對數值進行散列

>>> hash(1)
1
>>> hash(10)
10
>>> hash(10.00)
10
>>> hash(10.01)
230584300921368586
>>> hash(-10.01)
-230584300921368586

  如果你好奇為什么這些hash值看起來有不同的長度,請記住在Python中hash()函數返回的是整數對象,他們都會在標准的64位Python解釋器中通過24字節呈現。

  正如你可以看到的,整數的哈希值默認的就是它本身,不管你哈希的數據類型是什么,所以整數1和浮點數1.0哈希值都是1.

  這個有什么特殊的么,這個展示了你之前學到的知識,也就是hash函數經常是單向的函數,如果兩個不同的對象具有相同的hash值,根本不可能做反向工程,也就是從hash值返回到原始的對象,這也就使得被hash的原始數據類型的信息被丟失。

  另幾個有趣的可以關注的hash事情是,小數的散列值不同於它本身,負數具有負的哈希值。還有一點,就是如果兩個對象具有相同的哈希值,稱為哈希碰撞。

哈希一個字符串不同於對數值進行哈希。

>>> hash("Bad Behaviour")
7164800052134507161

  DOS攻擊(DOS代表拒絕服務)是指攻擊者故意耗盡計算機系統的資源,使系統不再能夠向客戶端提供服務的攻擊。在Scott Crosby演示的這個攻擊的具體案例中,攻擊可能會使目標系統充斥大量的數據,這些數據的哈希沖突導致目標系統使用更多的計算能力來解決沖突。

Python中可以hash的數據類型


在Python中,僅僅只有不可變數據類型可以被hash,然而每個自定義的對象在Python中都可以被hash,默認的他們的hash值是由他們的id派生的。也就意味着,同一個類的兩個不同實例,默認的是得到不同的hash值

>>> class Car():
...     velocity = 0
...     direction = 0
...     damage = 0
...
>>> first_car = Car()
>>> second_car = Car()
>>> hash(first_car)
274643597
>>> hash(second_car)
274643604

哈希表


現在你知道了什么是哈希函數,現在可以檢測哈希表,哈希表是一個數據結構可以儲存一堆鍵值對。

在哈希表中,鍵值對的所有建必須是可以哈希的,因為存儲的對是通過使用其鍵的散列索引的。哈希表十分有用,Hash tables are very useful because the average number of instructions that are necessary to lookup an element of the table is independent of the number of elements stored in the table itself.哈希表非常有用,因為查找表中某個元素所需的平均指令數量與表中存儲的元素數量無關,這就表明了不管你的表增長到成百上千次,查找特定元素的速度不會受到影響。

哈希表通常是通過創建可變數量的存儲桶來實現的,這些存儲桶將包含您的數據,並通過哈希它們的鍵對這些數據進行索引。鍵的散列值將確定用於特定數據段的正確存儲桶。

import pprint

class Hashtable:
    def __init__(self, elements):
        self.bucket_size = len(elements)
        self.buckets = [[] for i in range(self.bucket_size)]
        self._assign_buckets(elements)

    def _assign_buckets(self, elements):
        for key, value in elements:
            hashed_value = hash(key)
            index = hashed_value % self.bucket_size
            self.buckets[index].append((key, value))

    def get_value(self, input_key):
        hashed_value = hash(input_key)
        index = hashed_value % self.bucket_size
        bucket = self.buckets[index]
        for key, value in bucket:
            if key == input_key:
                return(value)
        return None

    def __str__(self):
        return pprint.pformat(self.buckets) # here pformat is used to return a printable representation of the object

if __name__ == "__main__":
     capitals = [
        ('France', 'Paris'),
        ('United States', 'Washington D.C.'),
        ('Italy', 'Rome'),
        ('Canada', 'Ottawa')
    ]
hashtable = Hashtable(capitals)
print(hashtable)
print(f"The capital of Italy is {hashtable.get_value('Italy')}")

Moreover, the more you increase the number of buckets you will handle, the more space you will waste. To test this you can simply change the bucket size of your previous example using a number of buckets that is two times the length of the input list:

此外,處理的桶數增加越多,浪費的空間就越多。要測試這一點,只需使用輸入列表長度的兩倍的桶數來更改上一個示例的桶大小

兩個散列值發生碰撞,將會存儲到同一個桶中,因為沖突不可避免,實現一個哈希表就得有一個解決沖突的方法。

通常在哈希表解決沖突的常用策略是:

  • open addressing 開放尋址法
  • separate chaining 鏈地址法

連地址法是您在上面的示例中已經實現的,它由使用另一個數據結構在同一個bucket中創建一個值鏈組成。在那個示例中,您使用了一個嵌套列表,當在超額占用的bucket中查找特定值時,必須對該列表進行完全掃描。

在開放尋址策略中,如果您應該使用的bucket是忙碌的,那么您只需繼續搜索要使用的新bucket。要實現這個解決方案,您需要對為新元素分配bucket的方式和檢索鍵值的方式進行一些更改。從assign buckets()函數開始,您必須使用默認值初始化您的bucket,並且如果您應該使用的bucket已經被占用,則繼續尋找空的bucket

Dictionaries in Python are built using hash tables and the **open addressing** collision resolution method.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM