github.com/scottcagno/storage@v1.8.0/pkg/generic/cache/lru.go (about)

     1  package cache
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sync"
     7  )
     8  
     9  // DefaultSize is the max size of the cache before
    10  // the older items automatically get evicted
    11  const DefaultSize = 256
    12  
    13  // item is an item in the cache (doubly linked)
    14  type item[K comparable, V any] struct {
    15  	key        K
    16  	value      V
    17  	prev, next *item[K, V]
    18  }
    19  
    20  func (i *item[K, V]) String() string {
    21  	ss := fmt.Sprintf("item[key=%v, value=%v, prev=%p, next=%p]", i.key, i.value, i.prev, i.next)
    22  	return ss
    23  }
    24  
    25  // LRU is an LRU cache
    26  type LRU[K comparable, V any] struct {
    27  	size       int               // max num of items
    28  	items      map[K]*item[K, V] // actives items
    29  	head, tail *item[K, V]       // head and tail of list
    30  	mu         sync.RWMutex
    31  }
    32  
    33  func NewLRU[K comparable, V any](size int) *LRU[K, V] {
    34  	if size < 1 {
    35  		size = DefaultSize
    36  	}
    37  	lru := &LRU[K, V]{
    38  		size:  size,
    39  		items: make(map[K]*item[K, V], size),
    40  		head:  new(item[K, V]),
    41  		tail:  new(item[K, V]),
    42  	}
    43  	lru.head.next = lru.tail
    44  	lru.tail.prev = lru.head
    45  	return lru
    46  }
    47  
    48  func (l *LRU[K, V]) init(size int) {
    49  	if l.size < 1 {
    50  		size = DefaultSize
    51  	}
    52  	l.size = size
    53  	l.items = make(map[K]*item[K, V], size)
    54  	l.head = new(item[K, V])
    55  	l.tail = new(item[K, V])
    56  	l.head.next = l.tail
    57  	l.tail.prev = l.head
    58  }
    59  
    60  // evict pops, removes and returns (effectively evicting) an item
    61  func (l *LRU[K, V]) evict() *item[K, V] {
    62  	i := l.tail.prev
    63  	l.pop(i)
    64  	delete(l.items, i.key)
    65  	return i
    66  }
    67  
    68  // pop, stack operation
    69  func (l *LRU[K, V]) pop(i *item[K, V]) {
    70  	i.prev.next = i.next
    71  	i.next.prev = i.prev
    72  }
    73  
    74  // push, stack operation
    75  func (l *LRU[K, V]) push(i *item[K, V]) {
    76  	l.head.next.prev = i
    77  	i.next = l.head.next
    78  	i.prev = l.head
    79  	l.head.next = i
    80  }
    81  
    82  // Resize sets the max size of the LRU cache and returns the evicted items. It will panic
    83  // if the size is less than one item. I the value is less than the number of items in the
    84  // cache, then items will be evicted.
    85  func (l *LRU[K, V]) Resize(size int) (ekeys, evals []interface{}) {
    86  	l.mu.Lock()
    87  	defer l.mu.Unlock()
    88  	if size < 1 {
    89  		log.Panicln("invalid size")
    90  	}
    91  	for size < len(l.items) {
    92  		i := l.evict()
    93  		ekeys, evals = append(ekeys, i.key), append(evals, i.value)
    94  	}
    95  	l.size = size
    96  	return ekeys, evals
    97  }
    98  
    99  // Len returns the current length of the cache
   100  func (l *LRU[K, V]) Len() int {
   101  	l.mu.Lock()
   102  	defer l.mu.Unlock()
   103  	return len(l.items)
   104  }
   105  
   106  // SetEvicted inserts or replaces a value for a given key.
   107  // The item is returned if this operation causes an eviction.
   108  func (l *LRU[K, V]) SetEvicted(key K, value V) (prev V, replaced bool, ekey K, eval V, evicted bool) {
   109  	l.mu.Lock()
   110  	defer l.mu.Unlock()
   111  	if l.items == nil {
   112  		l.init(l.size)
   113  	}
   114  	i := l.items[key]
   115  	if i == nil {
   116  		if len(l.items) == l.size {
   117  			i = l.evict()
   118  			ekey, eval, evicted = i.key, i.value, true
   119  		} else {
   120  			i = new(item[K, V])
   121  		}
   122  		i.key, i.value = key, value
   123  		l.push(i)
   124  		l.items[key] = i
   125  	} else {
   126  		prev, replaced = i.value, true
   127  		i.value = value
   128  		if l.head.next != i {
   129  			l.pop(i)
   130  			l.push(i)
   131  		}
   132  	}
   133  	return prev, replaced, ekey, eval, evicted
   134  }
   135  
   136  // Set inserts or replaces a value for the given key
   137  func (l *LRU[K, V]) Set(key K, value V) (V, bool) {
   138  	prev, replaced, _, _, _ := l.SetEvicted(key, value)
   139  	return prev, replaced
   140  }
   141  
   142  // Get returns a value for the given key (if it exists)
   143  func (l *LRU[K, V]) Get(key K) (V, bool) {
   144  	l.mu.Lock()
   145  	defer l.mu.Unlock()
   146  	i := l.items[key]
   147  	if i == nil {
   148  		return *new(V), false
   149  	}
   150  	if l.head.next != i {
   151  		l.pop(i)
   152  		l.push(i)
   153  	}
   154  	return i.value, true
   155  }
   156  
   157  // Del removes and value for the given key (if it exists)
   158  func (l *LRU[K, V]) Del(key K) (V, bool) {
   159  	l.mu.Lock()
   160  	defer l.mu.Unlock()
   161  	i := l.items[key]
   162  	if i == nil {
   163  		return *new(V), false
   164  	}
   165  	delete(l.items, key)
   166  	l.pop(i)
   167  	return i.value, true
   168  }
   169  
   170  // Range iterates over all keys and values in the order of most
   171  // recently used to least recently used items.
   172  func (l *LRU[K, V]) Range(iter func(key K, value V) bool) {
   173  	l.mu.Lock()
   174  	defer l.mu.Unlock()
   175  	if head := l.head; head != nil {
   176  		i := head.next
   177  		for i != l.tail {
   178  			if !iter(i.key, i.value) {
   179  				return
   180  			}
   181  			i = i.next
   182  		}
   183  	}
   184  }
   185  
   186  // Reverse iterates over all keys and values in the order of least
   187  // recently used to most recently used items.
   188  func (l *LRU[K, V]) Reverse(iter func(key K, value V) bool) {
   189  	l.mu.Lock()
   190  	defer l.mu.Unlock()
   191  	if tail := l.tail; tail != nil {
   192  		i := tail.prev
   193  		for i != l.head {
   194  			if !iter(i.key, i.value) {
   195  				return
   196  			}
   197  			i = i.prev
   198  		}
   199  	}
   200  }
   201  
   202  func (l *LRU[K, V]) String() string {
   203  	ss := fmt.Sprintf("lur:\n")
   204  	ss += fmt.Sprintf("\tsize=%d\n", l.size)
   205  	ss += fmt.Sprintf("\thead=%s\n", l.head)
   206  	ss += fmt.Sprintf("\ttail=%s\n", l.tail)
   207  	return ss
   208  	//size       int
   209  	//items      map[K]*item[K, V]
   210  	//head *item[K, V]
   211  	//tail *item[K, V]
   212  }