golang 提供了幾個簡單的容器供咱們使用,本文在介紹幾種Golang 容器的基礎上,實現一個基於Golang 容器的LRU算法。node
Golang 容器位於 container 包下,提供了三種包供咱們使用,heap、list、ring. 下面咱們分別學習。golang
heap 是一個堆的實現。一個堆正常保證了獲取/彈出最大(最小)元素的時間爲log n、插入元素的時間爲log n.
golang的堆實現接口以下:算法
// src/container/heap.go type Interface interface { sort.Interface Push(x interface{}) // add x as element Len() Pop() interface{} // remove and return element Len() - 1. }
heap 是基於 sort.Interface 實現的。緩存
// src/sort/ type Interface interface { // Len is the number of elements in the collection. Len() int // Less reports whether the element with // index i should sort before the element with index j. Less(i, j int) bool // Swap swaps the elements with indexes i and j. Swap(i, j int) }
所以,若是要使用官方提供的heap,須要咱們實現以下幾個接口:安全
Len() int {} // 獲取元素個數 Less(i, j int) bool {} // 比較方法 Swap(i, j int) // 元素交換方法 Push(x interface{}){} // 在末尾追加元素 Pop() interface{} // 返回末尾元素
而後在使用時,咱們能夠使用以下幾種方法:學習
// 初始化一個堆 func Init(h Interface){} // push一個元素倒堆中 func Push(h Interface, x interface{}){} // pop 堆頂元素 func Pop(h Interface) interface{} {} // 刪除堆中某個元素,時間複雜度 log n func Remove(h Interface, i int) interface{} {} // 調整i位置的元素位置(位置I的數據變動後) func Fix(h Interface, i int){}
list 實現了一個雙向鏈表,鏈表不須要實現heap 相似的接口,能夠直接使用。指針
鏈表的構造:code
// 返回一個鏈表對象 func New() *List {}
官方提供了豐富的方法供咱們操做列表,方法以下:對象
// 返回鏈表的長度 func (l *List) Len() int {} // 返回鏈表中的第一個元素 func (l *List) Front() *Element {} // 返回鏈表中的末尾元素 func (l *List) Back() *Element {} // 移除鏈表中的某個元素 func (l *List) Remove(e *Element) interface{} {} // 在表頭插入值爲 v 的元素 func (l *List) PushFront(v interface{}) *Element {} // 在表尾插入值爲 v 的元素 func (l *List) PushBack(v interface{}) *Element {} // 在mark以前插入值爲v 的元素 func (l *List) InsertBefore(v interface{}, mark *Element) *Element {} // 在mark 以後插入值爲 v 的元素 func (l *List) InsertAfter(v interface{}, mark *Element) *Element {} // 移動e某個元素到表頭 func (l *List) MoveToFront(e *Element) {} // 移動e到隊尾 func (l *List) MoveToBack(e *Element) {} // 移動e到mark以前 func (l *List) MoveBefore(e, mark *Element) {} // 移動e 到mark 以後 func (l *List) MoveAfter(e, mark *Element) {} // 追加到隊尾 func (l *List) PushBackList(other *List) {} // 將鏈表list放在隊列前 func (l *List) PushFrontList(other *List) {}
咱們能夠經過 Value 方法訪問 Element 中的元素。除此以外,咱們還能夠用下面方法作鏈表遍歷:接口
// 返回下一個元素 func (e *Element) Next() *Element {} // 返回上一個元素 func (e *Element) Prev() *Element {}
下面是隊列的遍歷的例子:
// l 爲隊列, for e := l.Front(); e != nil; e = e.Next() { //經過 e.Value 作數據訪問 }
container 中的循環列表是採用鏈表實現的。
// 構造一個包含N個元素的循環列表 func New(n int) *Ring {} // 返回列表下一個元素 func (r *Ring) Next() *Ring {} // 返回列表上一個元素 func (r *Ring) Prev() *Ring {} // 移動n個元素 (能夠前移,能夠後移) func (r *Ring) Move(n int) *Ring {} // 把 s 連接到 r 後面。若是s 和r 在一個ring 裏面,會把r到s的元素從ring 中刪掉 func (r *Ring) Link(s *Ring) *Ring {} // 刪除n個元素 (內部就是ring 移動n個元素,而後調用Link) func (r *Ring) Unlink(n int) *Ring {} // 返回Ring 的長度,時間複雜度 n func (r *Ring) Len() int {} // 遍歷Ring,執行 f 方法 (不建議內部修改ring) func (r *Ring) Do(f func(interface{})) {}
訪問Ring 中元素,直接 Ring.Value 便可。
下面,咱們經過map 和 官方包中的雙向鏈表實現一個簡單的lru 算法,用來熟悉golang 容器的使用。
LRU 算法 (Least Recently Used),在作緩存置換時用的比較多。逐步淘汰最近未使用的cache,而使咱們的緩存中持續保持着最近使用的數據。
package main import "fmt" import "container/list" // lru 中的數據 type Node struct { K, V interface{} } // 鏈表 + map type LRU struct { list *list.List cacheMap map[interface{}]*list.Element Size int } // 初始化一個LRU func NewLRU(cap int) *LRU { return &LRU{ Size: cap, list: list.New(), cacheMap: make(map[interface{}]*list.Element, cap), } } // 獲取LRU中數據 func (lru *LRU) Get(k interface{}) (v interface{}, ret bool) { // 若是存在,則把數據放到鏈表最前面 if ele, ok := lru.cacheMap[k]; ok { lru.list.MoveToFront(ele) return ele.Value.(*Node).V, true } return nil, false } // 設置LRU中數據 func (lru *LRU) Set(k, v interface{}) { // 若是存在,則把數據放到最前面 if ele, ok := lru.cacheMap[k]; ok { lru.list.MoveToFront(ele) ele.Value.(*Node).V = v // 更新數據值 return } // 若是數據是滿的,先刪除數據,後插入 if lru.list.Len() == lru.Size { last := lru.list.Back() node := last.Value.(*Node) delete(lru.cacheMap, node.K) lru.list.Remove(last) } ele := lru.list.PushFront(&Node{K: k, V: v}) lru.cacheMap[k] = ele }