雙向鏈表做爲在平常開發中最經常使用的數據結構之一,應用十分普遍,在諸多著名開源項目中如redis的list結構, groupcache的lru中均是核心實現。在設計此類數據集合的時候,外面看上去鏈表彷佛與數組類似,但鏈表是一個非連續性內存的存儲方案,提供了高效的節點重排能力與順序訪問方式,對比與操做數組,只需知道給定項的地址和位置的連接就能在內存中找到它,而且能夠經過增減節點的方式來靈活的調整長度。反而數組,好比插入一個新的元素,那麼該位置後的元素都要日後移動一位。git
redis 中的雙向鏈表
golang 中的雙向鏈表github
其結構如圖
golang
在雙向鏈表中頭結點的前指針爲空,尾節點的後指針爲空, 對頭尾的操做十分簡單, 插入頭節點只須要將新節點的next設置爲當前鏈表的頭節點, 當前列表的prev爲新節點, 並與之交換位置便可, 插入尾部反之
redis
在節點中間按遊標插入的話則須要考慮正向反向的問題, 下圖當i 爲正數表示正向插入,負數反向插入, 其實無論是隻操做頭尾節點仍是中間節點,其核心就是交換當前節點與前一個和後一個節點之間的連接
數組
將某個節點移動至頭部跟插入頭部動做多了一步交換當前節點先後節點連接的操做
緩存
而刪除某個節點就只須要將其先後節點的連接互相相連,使其不被引用,它會自動被回收掉
安全
LRU全稱Least Recently Used, 直譯爲「最近最少使用」, 其對於內存管理方面十分有效,好比容量只有十的一個集合,當寫入第十一條數據時候,最少使用的那個數據將會被淘汰,故此方法很適用於對有給定容量限制的熱數據作緩存管理數據結構
在開源項目groupcache中, 緩存的過時沒有設置過時時間而是依賴於LRU淘汰機制,那麼其用來實現LRU的核心就是一個雙向鏈表, 爲了保證效率, 緩存數據被保存在一個Map中使每次緩存的存取時間複雜度爲O(1), 而雙向鏈表則負責管理內存的容量以及實現淘汰機制
併發
在寫入新的緩存項時,會把其插入至鏈表的頭部, 而且判斷若是當前鏈表長度大於給定長度時,刪除鏈表尾部的元素,同時刪除其在map中的key
spa
每當有訪問命中緩存時, 會將命中的緩存移至鏈表頭部
上述插入和命中時將其放到鏈表頭部的策略,使得鏈表尾部的元素永遠是使用得最少的那個緩存,故新緩存進來時就將其淘汰。
本文說明了雙向鏈表的實現以及其實際應用,可是在真實應用中,golang 的雙向鏈表是非線程安全的,如遇到併發狀況操做鏈表則會由於找不到地址而報錯, 因此groupcache項目在從LRU策略中獲取緩存的時候,在外部包了一個帶讀寫鎖的結構體來保證其併發安全