lrucache.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"container/list"
   7  	"sync"
   8  )
   9  
  10  // LRUCache provides a thread-safe LRU cache with configurable max size.
  11  // It starts empty and grows on demand up to maxSize. When at capacity,
  12  // the least recently used entry is evicted to make room for new entries.
  13  type LRUCache[K comparable, V any] struct {
  14  	mu      sync.Mutex
  15  	items   map[K]*list.Element
  16  	order   *list.List // Front = most recent, Back = least recent
  17  	maxSize int
  18  }
  19  
  20  // lruEntry holds a key-value pair for the LRU list.
  21  type lruEntry[K comparable, V any] struct {
  22  	key   K
  23  	value V
  24  }
  25  
  26  // NewLRUCache creates a new LRU cache with the given maximum size.
  27  // The cache starts empty and grows on demand.
  28  func NewLRUCache[K comparable, V any](maxSize int) *LRUCache[K, V] {
  29  	if maxSize <= 0 {
  30  		maxSize = 1000 // Default minimum
  31  	}
  32  	return &LRUCache[K, V]{
  33  		items:   make(map[K]*list.Element),
  34  		order:   list.New(),
  35  		maxSize: maxSize,
  36  	}
  37  }
  38  
  39  // Get retrieves a value by key and marks it as recently used.
  40  // Returns the value and true if found, zero value and false otherwise.
  41  func (c *LRUCache[K, V]) Get(key K) (value V, found bool) {
  42  	c.mu.Lock()
  43  	defer c.mu.Unlock()
  44  
  45  	if elem, ok := c.items[key]; ok {
  46  		c.order.MoveToFront(elem)
  47  		entry := elem.Value.(*lruEntry[K, V])
  48  		return entry.value, true
  49  	}
  50  	var zero V
  51  	return zero, false
  52  }
  53  
  54  // Put adds or updates a value, evicting the LRU entry if at capacity.
  55  func (c *LRUCache[K, V]) Put(key K, value V) {
  56  	c.mu.Lock()
  57  	defer c.mu.Unlock()
  58  
  59  	// Update existing entry
  60  	if elem, ok := c.items[key]; ok {
  61  		c.order.MoveToFront(elem)
  62  		elem.Value.(*lruEntry[K, V]).value = value
  63  		return
  64  	}
  65  
  66  	// Evict LRU if at capacity
  67  	if len(c.items) >= c.maxSize {
  68  		oldest := c.order.Back()
  69  		if oldest != nil {
  70  			entry := oldest.Value.(*lruEntry[K, V])
  71  			delete(c.items, entry.key)
  72  			c.order.Remove(oldest)
  73  		}
  74  	}
  75  
  76  	// Add new entry
  77  	entry := &lruEntry[K, V]{key: key, value: value}
  78  	elem := c.order.PushFront(entry)
  79  	c.items[key] = elem
  80  }
  81  
  82  // Delete removes an entry from the cache.
  83  func (c *LRUCache[K, V]) Delete(key K) {
  84  	c.mu.Lock()
  85  	defer c.mu.Unlock()
  86  
  87  	if elem, ok := c.items[key]; ok {
  88  		delete(c.items, key)
  89  		c.order.Remove(elem)
  90  	}
  91  }
  92  
  93  // Len returns the current number of entries in the cache.
  94  func (c *LRUCache[K, V]) Len() int {
  95  	c.mu.Lock()
  96  	defer c.mu.Unlock()
  97  	return len(c.items)
  98  }
  99  
 100  // MaxSize returns the maximum capacity of the cache.
 101  func (c *LRUCache[K, V]) MaxSize() int {
 102  	return c.maxSize
 103  }
 104  
 105  // Clear removes all entries from the cache.
 106  func (c *LRUCache[K, V]) Clear() {
 107  	c.mu.Lock()
 108  	defer c.mu.Unlock()
 109  	c.items = make(map[K]*list.Element)
 110  	c.order.Init()
 111  }
 112  
 113  // Contains returns true if the key exists in the cache without updating LRU order.
 114  func (c *LRUCache[K, V]) Contains(key K) bool {
 115  	c.mu.Lock()
 116  	defer c.mu.Unlock()
 117  	_, ok := c.items[key]
 118  	return ok
 119  }
 120