从何时你也忌讳空山无人,从何时开始|

Drunker•L

园龄:4个月粉丝:0关注:0

2024-11-20 21:14阅读: 20评论: 0推荐: 0

二、LRU缓存淘汰策略

package lru

import "container/list"

//lru 缓存,并发访问不安全


type Cache struct {
	maxBytes  int64                         // 缓存的最大允许使用字节数
	nbytes    int64                         // 当前缓存占用的字节数
	ll        *list.List                    // 维护缓存中元素的顺序
	cache     map[string]*list.Element      // 哈希表旨在O(1)时间内找到key对应的value
	OnEvicted func(key string, value Value) //可选,当一个键值对被移除时调用
}

type entry struct {
	key   string
	value Value
}

type Value interface {
	Len() int
}

// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
	return &Cache{
		maxBytes:  maxBytes,
		ll:        list.New(),
		cache:     make(map[string]*list.Element),
		OnEvicted: onEvicted,
	}
}

// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value) {
	if ele, ok := c.cache[key]; ok { // 检查缓存,如果key已经存在,就将其移至队首并更新其value
		c.ll.MoveToFront(ele)
		kv := ele.Value.(*entry) // 类型断言,将ele转化为entry
		c.nbytes += int64(value.Len()) - int64(kv.value.Len())
		kv.value = value
	} else {
		ele := c.ll.PushFront(&entry{key, value})
		c.cache[key] = ele
		c.nbytes += int64(len(key)) + int64(value.Len())
	}
	for c.maxBytes != 0 && c.maxBytes < c.nbytes {
		c.RemoveOldest()
	}
}

// Get look up a key's value
func (c *Cache) Get(key string) (value Value, ok bool) {
	if ele, ok := c.cache[key]; ok {
		c.ll.MoveToFront(ele)    //如果当前元素被访问,认为最近被使用的概率更高,将元素移到队首
		kv := ele.Value.(*entry) // 将元素转化为entry
		return kv.value, true
	}
	return
}

// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
	ele := c.ll.Back() // 位于队尾的元素被认为是最不可能被访问的元素,将其删除
	if ele != nil {
		c.ll.Remove(ele)
		kv := ele.Value.(*entry)
		delete(c.cache, kv.key)
		c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
		if c.OnEvicted != nil {
			c.OnEvicted(kv.key, kv.value)
		}

	}
}

// Len the number of cache entries
func (c *Cache) Len() int {
	return c.ll.Len()
}

思考:

  1. 既然Get元素的时候,使用的是map进行查询,为什么要用一个双向链表来维护缓存中的元素的顺序?

    因为缓存的大小是有限的,维护元素的顺序可以使得经常被访问的元素置于队首,这样当缓存满了,就可以直接淘汰队尾的元素,以腾出空间给新的元素。这也是LRU算法的思想。

  2. 在Add方法中,既然设置了缓存的最大容量,那为什么还允许当前已使用的容量可以超过最大值,为什么不在Add的时候去检查,当前要存储的value是否有足够的空间分配,如果有就存储,如果没有,在去进行删除最旧的缓存项?

    1. 性能考虑(减少不必要的检查)
      • 在每次添加新数据时都进行空间检查,可能会引入额外的性能开销。尤其是在需要频繁更新缓存的场景下,检查是否有足够的空间可能会带来不必要的开销。相比之下,先添加数据,然后通过一个单独的逻辑来清理缓存,能够将检查和清理分开,减少每次添加数据时的复杂度。
    2. 批量删除缓存项的效率
      • 有些缓存实现会采用延迟删除策略,即不在每次添加时都做删除操作,而是在超出容量限制时统一进行删除。这样做可以提高删除操作的效率,因为每次删除操作会涉及到需要淘汰多少旧项的问题。如果每次添加时都进行删除,可能会导致频繁且不必要的删除操作,从而影响性能。
    3. 内存管理的灵活性
      • 在一些实现中,缓存的大小可能会动态增长并压缩。即使你设置了 maxBytes,缓存的大小可能会因为多次添加、删除等操作而波动。选择在缓存已满之后进行处理,可以保证缓存有足够的空间来容纳新的数据,并避免在每次添加时都进行检查。
    4. 实现简化
      • 有些实现中可能希望保持代码的简洁性,避免在每次添加数据时都需要考虑空间是否足够的问题。通过在容量超限时统一删除最旧项,这样实现起来可能更加直观且易于维护。

本文作者:Drunker•L

本文链接:https://www.cnblogs.com/drunkerl/articles/18559320

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Drunker•L  阅读(20)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起