停不下來使用的散列表

散列表(hash table)

       散列思想:散列表用的是數組支持按照下標隨機訪問數據的特性,所以散列表其實就是數組的一種擴展,由數組演化而來,如果沒有數組,就沒有散列表。
       散列表用的就是數組支持按照下標隨機訪問的時候,時間複雜度爲O(1)的特性,通過散列函數把元素的鍵值映射爲下標,然後將數據存儲在數組中對應下標的位置,當我們按照鍵值查詢元素時,用同樣的散列函數,將鍵值轉化數組下標,從對應的數組下標的位置取數據。
       散列函數僞代碼

int hash(String key) {
  // 獲取後兩位字符
  string lastTwoChars = key.substr(length-2, length);
  // 將後兩位字符轉換爲整數
  int hashValue = convert lastTwoChas to int-type;
  return hashValue;
}

       構造散列函數設計的基本要求
       1.散列函數計算得到的散列值是一個非負整數
       2.如果key1 = key2,那hash(key1) == hash(key2)
       3.如果 key1≠key2,那hash(key1)≠hash(key2) (這種基本無論什麼算法,都無法避免散列衝突)

散列衝突

       1.開放地址法
       如果出現了散列衝突,就重新探測一個空閒位置,將其插入,重新探測新的位置採用的探測方法是線性探測。(當某個數據經過散列函數,存儲位置已經佔用,就從當前位置開始,一次往後尋找,看時候有空閒位置,直到找到爲止)
       還有二次探測,所謂二次探測,跟線性探測很像,線性探測每次探測的步長是 1,那它探測的下標序列就是 hash(key)+0,hash(key)+1,hash(key)+2……而二次探測探測的步長就變成了原來的“二次方”,也就是說,它探測的下標序列就是 hash(key)+0,hash(key)+12,hash(key)+22……
       所謂雙重散列,意思就是不僅要使用一個散列函數。我們使用一組散列函數 hash1(key),hash2(key),hash3(key)……我們先用第一個散列函數,如果計算得到的存儲位置已經被佔用,再用第二個散列函數,依次類推,直到找到空閒的存儲位置

       我們用裝載因子來表示空位的多少
       裝載因子計算公式:
       散列表的裝載因子 = 填入表中的元素個數 / 散列表的長度

2.鏈表法(更加常用)

在這裏插入圖片描述

       當插入的時候,我們只需要通過散列函數計算出對應的散列槽位,其插入到對應鏈表中即可,所以插入的時間複雜度是 O(1)。

       如何設計散列函數
       散列函數的設計不能太複雜,其次,散列函數生成的值要儘可能隨機並且隨機分佈,這樣才能避免或者最小化散列衝突。
       裝載因子過大怎麼辦,裝載因子越大,說明散列表中的元素越多,空閒位置越少,散列衝突的概率越大,不僅插入數據的過程要多次尋址或者拉很長的鏈,查找的過程也會變得很慢。
       針對散列表,當裝載因子過大時,可以進行動態擴容。當然,對空間消耗敏感,也可以在裝載因子小於某個值的時候,啓動動態縮容。
避免低效的擴容,可以將擴容操作穿插在插入操作的過程中,分批完成,對於查詢操作,爲了兼容新,老散列表中的數據,先從新散列表中查找,再去老的散列表中查找。

    兩種使用場景

       使用開放地址法的是數據量比較小,裝載因子小的時候,適合採用開放尋址法。這也是java中ThreadLocalMap使用開放尋址法解決散列衝突的原因
       基於鏈表的散列衝突處理方法比較適合存儲大對象,大存儲量的散列表,而且,比起開放尋址法,更加靈活,支持更多優化策略,比如用紅黑樹代替鏈表。

HashMap就是一個工業級別的散列表
       1.初始大小
       默認初始大小爲16,這個默認值可以設置。
       2.裝載因子和動態擴容
       最大裝載因子默認0.75,當hashmap中元素個數超過0.75*capacity(容量)的時候,就會啓動擴容,每次擴容爲原來的兩倍大小
       3.散列衝突的解決方法
       採用鏈表法來解決衝突,1.8之後,當鏈表長度太長(超過8),鏈表轉爲紅黑樹,當小於8個的時候,轉爲鏈表。
       4.散列函數
       int hash(Object key) {
               int h = key.hashCode();
               return (h ^ (h >>> 16)) & (capitity -1); //capicity 表示散列表的大小
       }

LRU緩存淘汰算法

       一個緩存(cache)系統主要包含
       1.往緩存中添加一個數據
       2.從緩存中刪除一個數據
       3.在緩存中查找一個數據
       每個操作都設計“查找操作”,單純採用鏈表,時間複雜度只能是O(n),將散列表和鏈表兩種數據結構組合使用,可以將時間複雜度將爲O(1),

       使用雙向鏈表存儲數據。
       如何查找一個數據,通過散列表,在緩存中找到一個數據,找到數據之後,將他移動到雙向鏈表的尾部
       如何刪除一個數據,找到數據所在的節點,然後將結點刪除。
       如何添加一個數據,先看這個數據是否在緩存中,如果在,移動到雙向鏈表尾部,如果不在,就看緩存滿沒滿,如果滿了,則將雙向鏈表頭部的結點刪掉,然後將數據放到鏈表尾部,如果沒有,則直接放到尾部。

2.Redis有序集合

       2.1.什麼是有序集合?
       ①在有序集合中,每個成員對象有2個重要的屬性,即key(鍵值)和score(分值)。
       ②不僅會通過score來查找數據,還會通過key來查找數據。
       2.2.有序集合的操作有哪些?
       舉個例子,比如用戶積分排行榜有這樣一個功能:可以通過用戶ID來查找積分信息,也可以通過積分區間來查找用戶ID。這裏用戶ID就是key,積分就是score。所以,有序集合的操作如下:
①添加一個對象;
②根據鍵值刪除一個對象;
③根據鍵值查找一個成員對象;
④根據分值區間查找數據,比如查找積分在[100.356]之間的成員對象;
⑤按照分值從小到大排序成員變量。
       這時可以按照分值將成員對象組織成跳錶結構,按照鍵值構建一個散列表。那麼上面的所有操作都非常高效。

Java LinkedHashMap

       LinkedHashMap也是通過散列表和鏈表組合在一起實現的,實際上,不僅支持按照插入順序遍歷數據, 還支持按照訪問順序來遍歷數據。
       LinkedHashMap是通過雙向鏈表和散列表這兩種數據結構組合實現的,LinkedHashMap中的LInked實際上就是指的雙向鏈表,並非指用鏈表法解決散列衝突。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章