概述
Go缓存框架–手把手带你实现
源文章地址
文章目录
- Go缓存框架--手把手带你实现
-
- 引言:
-
- 实现之初
- LFU算法
- LRU
- 大体的思路
-
- 如何解决并发的问题
-
- 串行化一定不会发生并发的问题
- 解决缓存过期时间的问题
- 总结以及优化(重要)
引言:
首先简单的介绍一下这款缓存框架
- 它高效,功能强大,支持多线程
- 它是一个非常好的学习开源项目.这篇文章我将会介绍我开发时候的思路
- 它非常适合学习Go的特性.笔者使用channel 实现同步异步的获取值操作. 在设置和删除的时候是异步的.且不会发生并发问题. 你甚至可以修改源代码 自己写一些缓存算法来自己用.并且你不需要考虑加锁的问题.
- 我实现了LRU(最近最久未使用算法)和LFU(最近最少使用算法)
- 当然它也支持使过期时间.它的开发空间还很大.是一款非常棒非常棒的学习框架. 你甚至可以加入客户端服务端的代码 只要OK你都可以Request 然后也可以分布式 发挥你的想想
- 有一个goroutines 监听管道 cache向管道里写入命令. 执行者依次执行命令就可以了. redis也是一个单线程的执行器.
- 这里也使用了强大的缓存管理者
- 如果你希望提高你的Go编程功底.那么看完这篇文章吧!
- 如果你有好的想法,但是觉得不太会你可以私信我 我们一起探讨
- 项目已经开源放到github上, 并且有很详细的使用说明, 当然它也恨简单
- github源代码地址 <- 戳我
- 注: 再说一便我会把完整的代码思想都写出来 包括写完后测试优化 我将近优化了之前5倍的速度
实现之初
为什么想着实现一个缓存框架呢? 笔者最近也一直在看缓存. 然后也没有去实践.然后想着看看网上的go缓存框架.github上有一款. 我窃喜的阅读了源代码. 它的内容太少了 而且没有太多的学习的地方.所以我就想着自己实现一个. 首先我一开始想先从缓存的策略开始写起. 我的打算是先写两个 一个是LRU和LFU.
LRU算法是比较好实现, 所以我先从难的开始写起
LFU算法
如果这里不想看了 可以跳后面其实没啥关联
当然在LeetCode这个刷题网站就有这道题. 笔者是实现出来以后先去检测通过了再开始往下继续的
下面是笔者的A题记录
基本都在 %95以上
可以大致的讲一下思路: 如果有需要的小伙伴可以私我一下, 我单独写一篇分析LFU算法的也可以.
这里的代码是O(1)的时间复杂度
首先缓存你应该使用Hash表. 这里Hash表存key和 value value是一个结点的地址 结点里有数据的实例封装了一下.
LFU的淘汰策略是使用最少的, 那其实这里思想很简单. 有一个变量去记录了它的访问次数, 一开始是1, get以后直接+1. 然后需要淘汰的时候淘汰最少的.
基于这样的一个涉及思想 笔者一开始最先相先想到的是优先队列 最小堆.但是这样的时间复杂度就是O(logn)
伪O(1)的算法: 这个是再使用一个map 这个map的key是访问次数, 键是一个双向链表. 然后需要淘汰了.从最少的起 开始是1 for循环找到第一个不是空链的尾结点删除(因为先进入的是头结点. 尾结点说明它好久没访问了) 这样其实最好的情况就是O(1)
但是这样的情况其实是有断层的.最坏的情况 访问次数1-n的链表都是空的 这样时间复杂度就上升到了O(n)
O(1)的算法 基于上面的断层问题做处理就行了. 这里换一种数据结构. 用一个双向链表,这个双向链表的结点还是一个双向链表. 链表链.每次只从第一个链表的尾结点删除. 如果链表都是空了 删除这个链表 这样总能保证第一个链表里面是访问最少的.
过多的细节这里不再赘述
源代码如下
type LFUCache struct {
capacity int
size int
elements map[int]*doublyListNode
chain LFUChain
}
type LFUChain struct {
firstLinkedList *DoublyLinkedList
lastLinkedList *DoublyLinkedList
}
func (lc *LFUCache) Size() int {
return lc.size
}
func Constructor(capacity int) LFUCache {
lfuCache := LFUCache{
capacity: capacity,
size: 0,
elements: make(map[int]*doublyListNode, (capacity<<1|capacity)+1),
chain: LFUChain{
firstLinkedList: New(0),
lastLinkedList: New(0),
},
}
lfuCache.chain.firstLinkedList.next = lfuCache.chain.lastLinkedList
lfuCache.chain.lastLinkedList.prev = lfuCache.chain.firstLinkedList
return lfuCache
}
func (lc *LFUCache) Get(key int) int {
node, ok := lc.elements[key]
if !ok {
return -1
}
freqInc(node)
return node.data.(int)
}
func (lc *LFUCache) Put(key int, value int) {
if lc.capacity <= 0 {
return
}
node , ok := lc.elements[key]
if ok {
node.data = value
freqInc(node)
} else {
if lc.Size() >= lc.capacity {
listNodeChain := lc.chain.firstLinkedList.next
delNode, _ := listNodeChain.RemoveTail()
delete(lc.elements, delNode.key.(int))
lc.size--
if listNodeChain.IsEmpty() {
listNodeChain.LeaveForChain()
}
}
lfuNode := &doublyListNode{
key:key,
data:value,
freq:1,
}
lc.elements[key] = lfuNode
if lc.chain.firstLinkedList.next.freq != 1 {
tempList := New(1)
tempList.AddFirst(lfuNode)
insertChain(lc.chain.firstLinkedList, tempList)
} else {
lc.chain.firstLinkedList.next.AddFirst(lfuNode)
}
lc.size++
}
}
func freqInc(node *doublyListNode) {
parentList := node.parent
node.LeaveForList()
nextList := parentList.next
tempParent := parentList.prev
if parentList.IsEmpty() {
parentList.LeaveForChain()
parentList = tempParent
}
node.freq++
if nextList.freq != node.freq {
newChain := New(node.freq)
insertChain(parentList, newChain)
newChain.AddFirst(node)
node
最后
以上就是唠叨小土豆为你收集整理的go高效缓存框架教你实现Go缓存框架–手把手带你实现的全部内容,希望文章能够帮你解决go高效缓存框架教你实现Go缓存框架–手把手带你实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复