日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

19 | 散列表(中):如何打造一个工业级水平的散列表?

發布時間:2023/12/10 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 19 | 散列表(中):如何打造一个工业级水平的散列表? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

問題引入:如何實現一個工業級的散列表?

主要要求:

  • 設計一個合適的散列函數;
  • 定義裝載因子閾值,并且設計動態擴容策略;
  • 選擇合適的散列沖突解決方法。
  • 對于動態散列表來說,不管我們如何設計散列函數,選擇什么樣的散列沖突解決方法。隨著數據的不斷增加,散列表總會出現裝載因子過高的情況。這個時候,我們就需要啟動動態擴容。

    散列表碰撞攻擊原理

    如果精心構造數據,使得所有的數據經過散列函數之后,都散列到同一個槽里。當使用的是基于鏈表的沖突解決方法,散列表就會退化為鏈表,查詢的時間復雜度就從 O(1) 急劇退化為 O(n)。如果散列表中有 10 萬個數據,退化后的散列表查詢的效率就下降了 10 萬倍。如果之前運行 100 次查詢只需要 0.1 秒,那現在就需要 1 萬秒。因為查詢操作消耗大量 CPU 或者線程資源,導致系統無法響應其他請求,從而達到拒絕服務攻擊(DoS)的目的這

    如何設計一個可以應對各種異常情況的工業級散列表,來避免在散列沖突的情況下,散列表性能的急劇下降,并且能抵抗散列碰撞攻擊?

    好的散列函數:

    • 首先,不能太復雜。過于復雜的散列函數會消耗很多計算時間,影響到散列表性能。
    • 其次,散列函數生成的值要盡可能隨機并且均勻分布,避免或者最小化散列沖突,并且散列到每個槽里的數據也會比較平均,不會出現某個槽內數據特別多的情況。

    裝載因子過大了怎么辦?

    • 對于動態散列表來說,數據集合是頻繁變動的,無法預估將要加入的數據個數,無法事先申請一個足夠大的散列表。隨著數據加入,裝載因子慢慢變大。當裝載因子大到一定程度之后,散列沖突就會變得不可接受。這個時候如何處理呢?—— “動態擴容”嗎?
    • 如何做數組、棧、隊列的動態擴容的。針對散列表,當裝載因子過大時,也可以進行動態擴容,重新申請一個更大的散列表,將數據搬移到這個新散列表中。假設每次擴容我們都申請一個原來散列表大小兩倍的空間。那經過擴容之后,新散列表的裝載因子就下降為原來的一半。
    • 針對數組的擴容,數據搬移操作比較簡單。但是,針對散列表的擴容,數據搬移操作要復雜很多。因為散列表的大小變了,數據的存儲位置也變了,所以我們需要通過散列函數重新計算每個數據的存儲位置。

    對于支持動態擴容的散列表,插入操作的時間復雜度是多少呢?插入一個數據,最好情況下,不需要擴容,最好時間復雜度是 O(1)。最壞情況下,散列表裝載因子過高,啟動擴容,重新申請內存空間,重新計算哈希位置,并且搬移數據,所以時間復雜度是 O(n)。用攤還分析法,均攤情況下,時間復雜度接近最好情況,就是 O(1)。實際上,對于動態散列表,隨著數據的刪除,散列表中的數據會越來越少,空閑空間會越來越多。

    • 當散列表裝載因子超過某個閾值時需要進行擴容。裝載因子閾值需要選擇得當。如果太大,會導致沖突過多;如果太小,會導致內存浪費嚴重。裝載因子閾值的設置要權衡時間、空間復雜度。如果內存空間不緊張,對執行效率要求很高,可以降低負載因子的閾值;相反,如果內存空間緊張,對執行效率要求又不高,可以增加負載因子的值,甚至可以大于 1

    如何避免低效的擴容?

    大部分情況下,動態擴容的散列表插入一個數據都很快,但是在特殊情況下,當裝載因子已經到達閾值,需要先進行擴容,再插入數據。這個時候,插入數據就會變得很慢,甚至會無法接受。如果散列表當前大小為 1GB,要想擴容為原來的兩倍大小,那就需要對 1GB 的數據重新計算哈希值,并且從原來的散列表搬移到新的散列表很耗時

    如果我們的業務代碼直接服務于用戶,盡管大部分情況下,插入一個數據的操作都很快,但是,極個別非常慢的插入操作,也會讓用戶崩潰。這個時候,“一次性”擴容的機制就不合適了。為了解決一次性擴容耗時過多的情況,將擴容操作穿插在插入操作的過程中,分批完成。當裝載因子觸達閾值之后,只申請新空間,但并不將老的數據搬移到新散列表中當有新數據要插入時,將新數據插入新散列表中,并且從老的散列表中拿出一個數據放入到新散列表。每次插入一個數據到散列表,都重復上面的過程。經過多次插入操作之后,老散列表中數據就一點一點全部搬移到新散列表中。這樣沒有了集中的一次性數據搬移,插入操作就都變得很快了

    如何選擇沖突解決方法?

    開放尋址法和鏈表法在實際的軟件開發中都非常常用。Java LinkedHashMap 就采用了鏈表法解決沖突,ThreadLocalMap 是通過線性探測的開放尋址法來解決沖突。

    這兩種沖突解決方法各有什么優勢和劣勢,又各自適用哪些場景?

    1. 開放尋址法

    優點:

    • 開放尋址法不需要拉很多鏈表。散列表中的數據都存儲在數組中,可以有效地利用 CPU 緩存加快查詢速度。
    • 序列化起來比較簡單。鏈表法包含指針,序列化起來就沒那么容易。序列化很多場合都會用到。

    缺點:

    • 用開放尋址法解決沖突的散列表,刪除數據的時候比較麻煩,需要特殊標記已經刪除掉的數據。在開放尋址法中,所有的數據都存儲在一個數組中,比起鏈表法來說沖突的代價更高。
    • 使用開放尋址法解決沖突的散列表,裝載因子的上限不能太大。比鏈表法更浪費內存空間。

    總結一下“”當數據量比較小、裝載因子小的時候,適合采用開放尋址法。這也是 Java 中的ThreadLocalMap使用開放尋址法解決散列沖突的原因。

    2. 鏈表法:

    優點:

    • 鏈表法對內存的利用率比開放尋址法要高。鏈表結點可以在需要的時候再創建,并不需要事先申請。這也是鏈表優于數組的地方。
    • 鏈表法比起開放尋址法,對大裝載因子的容忍度更高。開放尋址法只能適用裝載因子小于 1 的情況。接近 1 時,就可能會有大量的散列沖突,導致大量的探測、再散列等,性能會下降很多。鏈表法只要散列函數的值隨機均勻,即便裝載因子變成 10,也就是鏈表的長度變長,雖然查找效率有所下降,但是比起順序查找還是快很多。

    缺點:

    • 鏈表因為要存儲指針,所以對于比較小的對象的存儲,是比較消耗內存的,還有可能會讓內存的消耗翻倍。
    • 鏈表中的結點是零散分布在內存中,不是連續的,對 CPU 緩存是不友好的,這方面對于執行效率也有一定的影響。

    當然,如果我們存儲的是大對象,存儲的對象的大小遠遠大于一個指針的大小(4個字節或者 8 個字節),那鏈表中指針的內存消耗在大對象面前就可以忽略了。實際上對鏈表法稍加改造,可以實現一個更加高效的散列表。將鏈表法中的鏈表改造為其他高效的動態數據結構,比如跳表、紅黑樹。這樣,即便出現散列沖突,極端情況下,所有的數據都散列到同一個桶內,那最終退化成的散列表的查找時間也只不過是 O(logn)。這樣也就有效避免了前面講到的散列碰撞攻擊。

    工業級散列表舉例分析-hashmap

    Java 中的 HashMa是怎么應用:

    1. 初始大小HashMap 默認的初始大小是 16,默認值是可以設置的,如果事先知道大概的數據量有多大,可以通過修改默認初始大小,減少動態擴容的次數,這樣會大大提高 HashMap 的性能。

    2. 裝載因子和動態擴容最大裝載因子默認是 0.75,當 HashMap 中元素個數超過 0.75*capacitycapacity 表示散列表的容量)的時候,就會啟動擴容,每次擴容都會擴容為原來的兩倍大小。

    3. 散列沖突解決方法HashMap 底層采用鏈表法來解決沖突。即使負載因子和散列函數設計得再合理,也免不了會出現拉鏈過長的情況,一旦出現拉鏈過長,則會嚴重影響 HashMap 的性能。在 JDK1.8 版本中,對 HashMap 做進一步優化引入了紅黑樹。而當鏈表長度太長(默認超過 8)時,鏈表就轉換為紅黑樹。可以利用紅黑樹快速增刪改查的特點,提高 HashMap 的性能。當紅黑樹結點個數少于 8 個的時候,又會將紅黑樹轉化為鏈表。因為在數據量較小的情況下,紅黑樹要維護平衡,比起鏈表來,性能上的優勢并不明顯。

    4. 散列函數散列函數的設計并不復雜,追求的是簡單高效、分布均勻。

    int hash(Object key)

    {

    ?int h = key.hashCode()

    return (h ^ (h >>> 16)) & (capicity -1); //capicity表示散列表的大小

    }

    其中,hashCode() 返回的是 Java 對象的 hash code

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的19 | 散列表(中):如何打造一个工业级水平的散列表?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。