107 lines
1.7 KiB
Go
107 lines
1.7 KiB
Go
package lru
|
|
|
|
import (
|
|
"container/list"
|
|
"time"
|
|
)
|
|
|
|
type LRU[T any] struct {
|
|
queue *list.List
|
|
items map[string]*list.Element
|
|
capacity int
|
|
}
|
|
type item[T any] struct {
|
|
key string
|
|
value T
|
|
eol time.Time
|
|
}
|
|
|
|
func New[T any](capacity int) *LRU[T] {
|
|
return &LRU[T]{
|
|
queue: list.New(),
|
|
items: make(map[string]*list.Element, capacity),
|
|
capacity: capacity,
|
|
}
|
|
}
|
|
|
|
func (lru *LRU[T]) Set(key string, value T, ttl time.Duration) (evicted bool) {
|
|
eol := time.Now().Add(ttl)
|
|
|
|
if element, ok := lru.items[key]; ok {
|
|
lru.queue.MoveToFront(element)
|
|
item := element.Value.(*item[T])
|
|
item.value = value
|
|
item.eol = eol
|
|
|
|
return
|
|
}
|
|
|
|
if lru.queue.Len() == lru.capacity {
|
|
lru.evict()
|
|
evicted = true
|
|
}
|
|
|
|
item := &item[T]{
|
|
key: key,
|
|
value: value,
|
|
eol: eol,
|
|
}
|
|
|
|
element := lru.queue.PushFront(item)
|
|
lru.items[item.key] = element
|
|
|
|
return
|
|
}
|
|
|
|
func (lru *LRU[T]) Get(name string) (value T, ok bool) {
|
|
element, ok := lru.items[name]
|
|
if !ok {
|
|
return value, false
|
|
}
|
|
|
|
item := element.Value.(*item[T])
|
|
if item.expired() {
|
|
lru.delete(element)
|
|
|
|
return value, false
|
|
}
|
|
|
|
lru.queue.MoveToFront(element)
|
|
|
|
return item.value, true
|
|
}
|
|
|
|
func (lru *LRU[T]) evict() {
|
|
element := lru.queue.Back()
|
|
if element == nil {
|
|
return
|
|
}
|
|
|
|
for { // find first expired
|
|
item := element.Value.(*item[T])
|
|
|
|
if item.expired() {
|
|
lru.delete(element)
|
|
|
|
return
|
|
}
|
|
|
|
element = element.Prev()
|
|
|
|
if element == nil {
|
|
break // probably expired not found
|
|
}
|
|
}
|
|
|
|
lru.delete(lru.queue.Back()) // delete oldest if no one expired
|
|
}
|
|
|
|
func (lru *LRU[T]) delete(element *list.Element) {
|
|
lru.queue.Remove(element)
|
|
delete(lru.items, element.Value.(*item[T]).key)
|
|
}
|
|
|
|
func (i *item[T]) expired() bool {
|
|
return time.Now().After(i.eol)
|
|
}
|