筚路蓝缕,以启山林。不鸣则已,一鸣惊人。
——《左转`宣公十二年》
目录
双向链表实现可移步:双向链表
LRU
LRU(Least Recently Used):是一种按访问时序的缓存淘汰策略:最近使用过的数据是有用的,很久没用的数据是无用的,内存满了时优先删除最久没用的那个数据。
情景再现:
举例说明下。假如你的手机运存所支持的当前缓存的最大后台程序数为4,你由久及近依次打开了计算器、微信、手电筒、日历四个程序,即最新打开的是日历,最久打开的是计算器。
操作1:你现在重新点开了“微信”,这时候你再查看后台当前运行的程序列表,由久及近应该会变为计算器、手电筒、日历、微信,即最新打开的“微信”程序被排在了开头, 其他的依次后退;
操作2:你现在打开了一个新程序“时钟”,那么后台程序列表将变成:手电筒、日历、微信、时钟 ,按照LRU策略,因为内存满了,最久没有使用的是计算器,系统会自动把计算器删除出去,时钟是新打开的所以排在了最开头,刚刚排在开头的日历排到了第二位。
操作3:我主动划走了(删除了)后台的日历程序,那么由久及近将变成:手电筒、微信、时钟
总结与设计
特点如下:
1,获取其中某个数据,该数据被提到开头,其他的继续按原顺序接上
2,这组数据有顺序,删除其中一个其他的继续按原顺序接上
3,这组数据如果满了(达到了存储它的最大长度),此时再往里添加一个数据,那么尾部最后一个数据(最久未使用的)将被删除,新添加的将放在首部
删除元素节点操作可以用链表,链表的删除和插入操作快并且是有顺序的;哈希表查找(访问)某个元素快,因此可将二者结合取长补短,形成哈希链表。 因此设计如下:
链表表首<-->[k1:v1]<-->[k2:v2]<-->链表表尾
哈希表存储结构:基于map,与链表节点映射。
操作:如删除k2,链表将变为链表表首<-->[k1:v1]<-->链表表尾,同时哈希表中也将只剩k1一个键。
实现LRU
// 链表操作接口
type ListInterface interface {
Add2Front(string) // 链表表首添加新元素
Remove(string) // 删除指定元素
RemoveLast(string) *Node // 删除最后一个元素
Len(List) int // 链表长度
}
const Max = 5 //定义缓存的最大容量
var hashMap map[string]*Node // 定义哈希表
var cache *List // 定义缓存
type Node struct {
Pre *Node
Next *Node
Data DataStruct
}
type DataStruct struct {
Key string
Value string
}
type List struct {
First *Node
Last *Node
Size int
}
// 删除链表尾节点
func (list *List) RemoveLast() *Node {
node := list.Last.Pre
lastNode := list.Last
node.Next = nil
list.Last = node
list.Size -= 1
return lastNode
}
// 删除指定节点
func (list *List) Remove(value string) {
current := list.First
for current != nil {
if current.Data.Key == value {
pre := current.Pre
next := current.Next
pre.Next = next
next.Pre = pre
}
current = current.Next
}
list.Size -= 1
}
// 添加新的首节点
func (list *List) Add2Front(key, value string) {
newNode := new(Node)
newNode.Data.Key = key
newNode.Data.Value = value
// 链表为空时
if list.Size < 1 {
list.First = newNode
list.Last = newNode
} else {
// 链表的长度>=1时
firstNode := list.First
firstNode.Pre = newNode
newNode.Next = firstNode
list.First = newNode
}
list.Size += 1
}
func (list *List) Len() int {
return list.Size
}
// 创建一个空的双链表
func CreateNewAirList() (list *List) {
return &List{}
}
// 访问/查找某个数据
func (cache *List) Get(key string) *Node {
if value, ok := hashMap[key]; ok { // 如果缓存中有
cache.Put(key, value.Data.Value) // 添加到表首
return value // 返回数据
}
return nil
}
// 添加数据/将数据提到开头
func (cache *List) Put(key, value string) {
node := new(Node)
node.Data.Key = key
node.Data.Value = value
if _, ok := hashMap[key]; ok { // 先检查,如果缓存中有
cache.Remove(key) // 删除旧数据
cache.Add2Front(key, value) // 添加到表首
hashMap[key] = node // 更新map中的值
} else {
if cache.Size == Max { // 添加时容量已满
cache.RemoveLast()
delete(hashMap, key)
}
cache.Add2Front(key, value) // 添加到表首
hashMap[key] = node //更新map中的值
}
}
// 删除某元素。要删除的后台程序必然是存在的
func (cache *List) RemoveApp(key string) {
cache.Remove(key)
delete(hashMap, key)
}
// 查看当前运行的后台程序列表
func (list *List) printNode() {
head := list.First
fmt.Print("\t\t")
for head != nil {
fmt.Print(head.Data, "\t")
head = head.Next
}
}
验证下我的骚操作
func main() {
cache = CreateNewAirList()
hashMap = make(map[string]*Node, 5)
/* 测试新增 */
cache.Put("日历", "日历value")
cache.Put("计算器", "计算器value")
cache.Put("微信", "微信value")
cache.Put("时钟", "时钟value") // 最新打开的程序在表头
fmt.Println("新开了四个程序,由近及久依次是:")
cache.printNode()
/* 测试访问 */
fmt.Println("\n微信已在缓存中,再次访问微信,后台程序由近及久依次是:")
cache.Get("微信") // 访问微信
cache.printNode() // 检查微信是否成了表头第一个程序,其他的顺序是否正确
fmt.Println("\n新开了个手电筒程序:")
cache.Put("手电筒", "手电筒value")
cache.printNode()
/* 测试新增让缓存占满 */
fmt.Println("当前缓存容量已满:", cache.Len(), " \n新开了个程序陌陌,后台程序由近及久依次是:")
cache.Put("陌陌", "陌陌value")
cache.printNode() //检查陌陌是不是在开头,表尾原来的日历是否没有了,其他几个的顺序是否正常的
/* 测试删除缓存中某程序:划走(删除)手电筒 */
fmt.Println("\n划走(删除)手电筒程序后:")
cache.RemoveApp("手电筒")
cache.printNode()
fmt.Println("\n划走(删除)时钟程序后:")
cache.RemoveApp("时钟")
cache.printNode()
}
控制台
新开了四个程序,由近及久依次是:
{时钟 时钟value} {微信 微信value} {计算器 计算器value} {日历 日历value}
微信已在缓存中,再次访问微信,后台程序由近及久依次是:
{微信 微信value} {时钟 时钟value} {计算器 计算器value} {日历 日历value}
新开了个手电筒程序:
{手电筒 手电筒value} {微信 微信value} {时钟 时钟value} {计算器 计算器value} {日历 日历value} 当前缓存容量已满: 5
新开了个程序陌陌,后台程序由近及久依次是:
{陌陌 陌陌value} {手电筒 手电筒value} {微信 微信value} {时钟 时钟value} {计算器 计算器value}
划走(删除)手电筒程序后:
{陌陌 陌陌value} {微信 微信value} {时钟 时钟value} {计算器 计算器value}
划走(删除)时钟程序后:
{陌陌 陌陌value} {微信 微信value} {计算器 计算器value}
Process finished with exit code 0