123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- package cache
- import (
- "context"
- "sort"
- "sync"
- "time"
- )
- // LRU represents an LRU (Least Recently Used) cache implementation.
- type LRU struct {
- // The maximum cache size in bytes. Default is 16MB.
- MaxSize int
- // The duration while an item is cached.
- ItemTTL time.Duration
- // The function called when an item is evicted.
- OnEvict func(key string, i Item)
- once sync.Once
- mutex sync.Mutex
- size int
- items map[string]*lruItem
- priority []*lruItem
- }
- func (c *LRU) Get(ctx context.Context, key string) (Item, bool) {
- c.once.Do(c.init)
- c.mutex.Lock()
- defer c.mutex.Unlock()
- i, isCached := c.items[key]
- if !isCached || i.expiresAt.Before(time.Now()) {
- return nil, false
- }
- i.count++
- return i.value, true
- }
- func (c *LRU) Set(ctx context.Context, key string, i Item) {
- c.once.Do(c.init)
- c.mutex.Lock()
- defer c.mutex.Unlock()
- if i, isCached := c.items[key]; isCached {
- i.expiresAt = time.Now()
- }
- if c.size+i.Size() > c.MaxSize {
- c.free(i.Size())
- }
- c.add(&lruItem{
- key: key,
- count: 1,
- expiresAt: time.Now().Add(c.ItemTTL),
- value: i,
- })
- }
- func (c *LRU) Del(ctx context.Context, key string) {
- c.once.Do(c.init)
- c.mutex.Lock()
- defer c.mutex.Unlock()
- if i, isCached := c.items[key]; isCached {
- i.expiresAt = time.Now()
- c.free(i.value.Size())
- }
- }
- func (c *LRU) Len() int {
- c.mutex.Lock()
- defer c.mutex.Unlock()
- return len(c.items)
- }
- func (c *LRU) Size() int {
- c.mutex.Lock()
- defer c.mutex.Unlock()
- return c.size
- }
- func (c *LRU) init() {
- if c.MaxSize <= 0 {
- c.MaxSize = 16000000
- }
- c.items = make(map[string]*lruItem, 64)
- c.priority = make([]*lruItem, 0, len(c.items))
- }
- func (c *LRU) free(size int) {
- now := time.Now()
- sortLRUItems(now, c.priority)
- for len(c.priority) != 0 {
- lastItem := c.priority[len(c.priority)-1]
- if lastItem.IsExpired(now) {
- c.removeLastItem()
- continue
- }
- if c.size+size <= c.MaxSize {
- return
- }
- c.removeLastItem()
- if c.OnEvict != nil {
- c.OnEvict(lastItem.key, lastItem.value)
- }
- }
- }
- func (c *LRU) removeLastItem() {
- i := len(c.priority) - 1
- item := c.priority[i]
- c.priority[i] = nil
- c.priority = c.priority[:i]
- delete(c.items, item.key)
- c.size -= item.value.Size()
- }
- func (c *LRU) add(i *lruItem) {
- c.items[i.key] = i
- c.priority = append(c.priority, i)
- c.size += i.value.Size()
- }
- type lruItem struct {
- key string
- count int
- expiresAt time.Time
- value Item
- }
- func (i *lruItem) priority(now time.Time) int {
- if i.IsExpired(now) {
- return 0
- }
- return i.count
- }
- func (i *lruItem) IsExpired(now time.Time) bool {
- return i.expiresAt.Before(now)
- }
- func sortLRUItems(now time.Time, v []*lruItem) {
- sort.Slice(v, func(a, b int) bool {
- return v[a].priority(now) > v[b].priority(now)
- })
- }
|