golang 容器的學習與實踐

golang 提供了幾個簡單的容器供咱們使用,本文在介紹幾種Golang 容器的基礎上,實現一個基於Golang 容器的LRU算法。node

容器介紹

Golang 容器位於 container 包下,提供了三種包供咱們使用,heap、list、ring. 下面咱們分別學習。golang

heap

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 鏈表

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 作數據訪問
}

ring 循環列表

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
}

其餘

  1. 上述的容器都不是goroutines 安全的
  2. 上面的lr 也不是goroutines 安全的
  3. Ring 中不建議在Do 方法中修改Ring 的指針,行爲是未定義的
相關文章
相關標籤/搜索