LRU Cache Interface or Template Go 代码实现

目录

1. LRU Cache

    1.1 interface 版本

    1.2 template 版本

2.  [可选] 用例拓展

    2.1 container/list.go

        2.1.2 list 数据结构

        2.1.2 list 使用例子

    2.2 transport.go connLRU

正文

1. LRU Cache

LRU 是个经典通用功能设计场景, 通用思路用 map 类似的结构来存储数据源, 用双向链表存储新旧关系链条.

1.1 interface 版本

package cache

import (
	"container/list"
	"sync"
)

type entry struct {
	key   interface{} // container list 链接 map[interface{}]*list.Element
	value interface{}
}

// CacheLUR
type CacheLRU struct {
	M sync.Mutex // 因为 Get 触发热度更新操作, 所以只能用互斥解决并发冲突

	cap  int                           // capacity 容量 <= 0 标识不限制
	list *list.List                    // 双向链表 entry container list
	data map[interface{}]*list.Element // 数据池子 entry::key -> list::value
}

// NewCacheLRU new CacheLRU return struct pointer
func NewCacheLRU(cap int) *CacheLRU {
	return &CacheLRU{
		cap:  cap,
		list: list.New(),
		data: make(map[interface{}]*list.Element),
	}
}

func (c *CacheLRU) Put(key, value interface{}) {
	element, ok := c.data[key]
	if ok {
		// 结点存在, 更新结点
		element.Value.(*entry).value = value
		// 调整 element 热度
		c.list.MoveToFront(element)
		return
	}

	// 容量不够走删除逻辑
	if c.cap > 0 && c.cap <= c.list.Len() {
		delete(c.data, c.list.Remove(c.list.Back()).(*entry).key)
	}
	// 容量足够开始走添加操作
	c.data[key] = c.list.PushFront(&entry{key: key, value: value})
}

func (c *CacheLRU) Get(key interface{}) (value interface{}, ok bool) {
	element, ok := c.data[key]
	if ok {
		// 获取指定缓存和值
		value = element.Value.(*entry).value
		// 调整 element 热度
		c.list.MoveToFront(element)
	}
	return
}

func (c *CacheLRU) Delete(key interface{}) {
	element, ok := c.data[key]
	if ok {
		delete(c.data, key)
		c.list.Remove(element)
	}
}

M sync.Mutex 互斥锁做线程安全, 需要就用; list 双向链表保存时间新老关系; map 可以让时间复杂度到 O(1).

使用教程: 

// 创建
c := cache.New(1)

// 设置
c.Put("123", "123")
c.Put("234", "234")

// 使用
fmt.Println(c.Get("123"))
fmt.Println(c.Get("234"))

// 删除
c.Delete("123")

1.2 template 版本

package cache

import (
	"container/list"
	"sync"
)

type pair[K comparable, V any] struct {
	key   K
	value V
}

// TemplateLRU go 1.18 对 LRU cache 包装
type TemplateLRU[K comparable, V any] struct {
	M sync.Mutex // 如果需要并发安全, 所有操作可以通过 M 加锁

	cap  int                 // capacity 容量 <= 0 标识不限制
	list *list.List          // entry container list
	data map[K]*list.Element // 数据池子
}

// NewTemplateLRU new cache LRU
func NewTemplateLRU[K comparable, V any](cap int) *TemplateLRU[K, V] {
	return &TemplateLRU[K, V]{
		cap:  cap,
		list: list.New(),
		data: make(map[K]*list.Element),
	}
}

// Get 获取 value, ok is true 标识存在
func (c *TemplateLRU[K, V]) Get(key K) (value V, ok bool) {
	element, ok := c.data[key]
	if ok {
		value = element.Value.(*pair[K, V]).value
		c.list.MoveToFront(element)
	}
	return
}

// Delete 通过 key 删除操作
func (c *TemplateLRU[K, V]) Delete(key K) {
	element, ok := c.data[key]
	if ok {
		delete(c.data, key)
		c.list.Remove(element)
	}
}

// Put 添加数据
func (c *TemplateLRU[K, V]) Put(key K, value V) {
	element, ok := c.data[key]
	if ok {
		element.Value.(*pair[K, V]).value = value
		c.list.MoveToFront(element)
		return
	}

	// 容量不足, 尝试清理数据
	if c.cap > 0 && c.cap <= c.list.Len() {
		delete(c.data, c.list.Remove(c.list.Back()).(*pair[K, V]).key)
	}

	// 开始填充
	c.data[key] = c.list.PushFront(&pair[K, V]{key: key, value: value})
}

2.  [可选] 用例拓展

2.1 container/list.go

2.1.1 list 数据结构

上面 LRU Cache 代码中主要用了 "container/list" 双向链表保存新旧关系. 没有去造轮子自己来实现链式关系.

type Node[K comparable, V any] struct {
	key   K
	value V
	prev  *Node[K, V]
	next  *Node[K, V]
} 

因为行家不喜欢总去当 🤡  . 简单去看看 contaner/list 数据结构布局.

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package list implements a doubly linked list.
//
// To iterate over a list (where l is a *List):
//    for e := l.Front(); e != nil; e = e.Next() {
//        // do something with e.Value
//    }
//
package list

// Element is an element of a linked list.
type Element struct {
    // Next and previous pointers in the doubly-linked list of elements.
    // To simplify the implementation, internally a list l is implemented
    // as a ring, such that &l.root is both the next element of the last
    // list element (l.Back()) and the previous element of the first list
    // element (l.Front()).
    next, prev *Element

    // The list to which this element belongs.
    list *List

    // The value stored with this element.
    Value interface{}
}

// Next returns the next list element or nil.
func (e *Element) Next() *Element {
    if p := e.next; e.list != nil && p != &e.list.root {
        return p
    }
    return nil
}

// Prev returns the previous list element or nil.
func (e *Element) Prev() *Element {
    if p := e.prev; e.list != nil && p != &e.list.root {
        return p
    }
    return nil
}

// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List struct {
    root Element // sentinel list element, only &root, root.prev, and root.next are used
    len  int     // current list length excluding (this) sentinel element
}

它是个特殊循环双向链表数据结构, 特殊之处在于 Element::List  指向头结点(root list). 属于 Go 自己的小聪明吧, 没事总要找点事情干干呗.

2.1.2 list 使用例子

func Test_List_Demo(t *testing.T) {
    // Persion 普通人
    type Persion struct {
        Name string
        Age  int
    }

    pers := list.New()

    // 链表数据填充
    pers.PushBack(&Persion{Name: "wang", Age: 35})
    pers.PushFront(&Persion{Name: "zhi", Age: 35})

    fmt.Printf("List Len() = %d\n", pers.Len())
    if pers.Len() != 2 {
        t.Fatal("pers.Len() != 2 data faild")
    }

    // 开始遍历数据
    for element := pers.Front(); element != nil; element = element.Next() {
        per, ok := element.Value.(*Persion)
        if !ok {
            panic(fmt.Sprint("Persion list faild", element.Value))
        }
        fmt.Println(per)
    }

    // 数据删除
    for element := pers.Front(); element != nil; {
        next := element.Next()
        pers.Remove(element)
        element = next
    }

    fmt.Printf("List Len() = %d\n", pers.Len())
    if pers.Len() != 0 {
        t.Fatal("pers.Len() != 0 data faild")
    }
}

单元测试结果: 

Running tool: /usr/local/go/bin/go test -timeout 30s -run ^Test_List_Demo$ demo/src/container/list -v -count=1

=== RUN   Test_List_Demo
List Len() = 2
&{zhi 35}
&{wang 35}
List Len() = 0
--- PASS: Test_List_Demo (0.00s)
PASS
ok      demo/src/container/list    0.002s

2.2 transport.go connLRU

list 在 Go 源码中一处小应用 

//
// src/net/http/transport.go
//


// persistConn wraps a connection, usually a persistent one
// (but may be used for non-keep-alive requests as well)
type persistConn struct {
  ... 
  ..
  . } type connLRU
struct { ll *list.List // list.Element.Value type is of *persistConn m map[*persistConn]*list.Element } // add adds pc to the head of the linked list. func (cl *connLRU) add(pc *persistConn) { if cl.ll == nil { cl.ll = list.New() cl.m = make(map[*persistConn]*list.Element) } ele := cl.ll.PushFront(pc) if _, ok := cl.m[pc]; ok { panic("persistConn was already in LRU") } cl.m[pc] = ele } func (cl *connLRU) removeOldest() *persistConn { ele := cl.ll.Back() pc := ele.Value.(*persistConn) cl.ll.Remove(ele) delete(cl.m, pc) return pc } // remove removes pc from cl. func (cl *connLRU) remove(pc *persistConn) { if ele, ok := cl.m[pc]; ok { cl.ll.Remove(ele) delete(cl.m, pc) } } // len returns the number of items in the cache. func (cl *connLRU) len() int { return len(cl.m) }

3. 结尾

很多代码, 很多事情在平淡无奇处用心琢磨 ~ 可能长久 ~ 互相勉励 good luckly ~  

posted on 2021-07-25 15:42  喜ω欢  阅读(243)  评论(1编辑  收藏  举报