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 }