golang 提供了几个简单的容器供我们使用,本文在介绍几种Golang 容器的基础上,实现一个基于Golang 容器的LRU算法。
容器介绍 
  
     
Golang 容器位于 container 包下,提供了三种包供我们使用,heap、list、ring. 下面我们分别学习。
heap 
  
     
heap 是一个堆的实现。一个堆正常保证了获取/弹出最大(最小)元素的时间为log n、插入元素的时间为log n.
golang的堆实现接口如下:
1
 2
 3
 4
 5
 6
  
// 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 实现的。
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
  
// 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,需要我们实现如下几个接口 :
1
 2
 3
 4
 5
  
Len ()  int  {}               // 获取元素个数 
Less ( i ,  j  int )  bool   {}    // 比较方法 
Swap ( i ,  j  int )             // 元素交换方法 
Push ( x  interface {}){}      // 在末尾追加元素 
Pop ()  interface {}          // 返回末尾元素  
 
然后在使用时,我们可以使用如下几种方法 :
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
  
// 初始化一个堆 
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 链表 
  
     
list 实现了一个双向链表,链表不需要实现heap 类似的接口,可以直接使用。
链表的构造和使用:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
  
  // 返回一个链表对象 
   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 中的元素。除此之外,我们还可以用下面方法做链表遍历:
1
 2
 3
 4
  
// 返回下一个元素 
func  ( e  * Element )  Next ()  * Element  {} 
// 返回上一个元素 
func  ( e  * Element )  Prev ()  * Element  {}  
 
队列的遍历:
1
 2
 3
 4
  
// l 为队列, 
for  e  :=  l . Front ();  e  !=  nil ;  e  =  e . Next ()  { 
  //通过 e.Value 做数据访问 
 }  
 
ring 循环列表 
  
     
container 中的循环列表是采用链表实现的。
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
  
// 构造一个包含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 即可。
容器的使用 
  
     
LRU 算法 (Least Recently Used),在做缓存置换时用的比较多。逐步淘汰最近未使用的cache,而使我们的缓存中持续保持着最近使用的数据。下面,我们通过map 和 官方包中的双向链表实现一个简单的lru 算法,用来熟悉golang 容器的使用。
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
  
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 
 }  
 
其他 
  
     
上述的容器都不是goroutines 安全的 
上面的lr 也不是goroutines 安全的 
Ring 中不建议在Do 方法中修改Ring 的指针,行为是未定义的