package lfu import ( "container/list" "sync" ) type Eviction struct { Key string Value interface{} } type Cache struct { // If len > UpperBound, cache will automatically evict // down to LowerBound. If either value is 0, this behavior // is disabled. UpperBound int LowerBound int values map[string]*cacheEntry freqs *list.List len int lock *sync.Mutex EvictionChannel chan<- Eviction } type cacheEntry struct { key string value interface{} freqNode *list.Element } type listEntry struct { entries map[*cacheEntry]byte freq int } func New() *Cache { c := new(Cache) c.values = make(map[string]*cacheEntry) c.freqs = list.New() c.lock = new(sync.Mutex) return c } func (c *Cache) Get(key string) interface{} { c.lock.Lock() defer c.lock.Unlock() if e, ok := c.values[key]; ok { c.increment(e) return e.value } return nil } func (c *Cache) Set(key string, value interface{}) { c.lock.Lock() defer c.lock.Unlock() if e, ok := c.values[key]; ok { // value already exists for key. overwrite e.value = value c.increment(e) } else { // value doesn't exist. insert e := new(cacheEntry) e.key = key e.value = value c.values[key] = e c.increment(e) c.len++ // bounds mgmt if c.UpperBound > 0 && c.LowerBound > 0 { if c.len > c.UpperBound { c.evict(c.len - c.LowerBound) } } } } func (c *Cache) Len() int { c.lock.Lock() defer c.lock.Unlock() return c.len } func (c *Cache) Evict(count int) int { c.lock.Lock() defer c.lock.Unlock() return c.evict(count) } func (c *Cache) evict(count int) int { // No lock here so it can be called // from within the lock (during Set) var evicted int for i := 0; i < count; { if place := c.freqs.Front(); place != nil { for entry, _ := range place.Value.(*listEntry).entries { if i < count { if c.EvictionChannel != nil { c.EvictionChannel <- Eviction{ Key: entry.key, Value: entry.value, } } delete(c.values, entry.key) c.remEntry(place, entry) evicted++ c.len-- i++ } } } } return evicted } func (c *Cache) increment(e *cacheEntry) { currentPlace := e.freqNode var nextFreq int var nextPlace *list.Element if currentPlace == nil { // new entry nextFreq = 1 nextPlace = c.freqs.Front() } else { // move up nextFreq = currentPlace.Value.(*listEntry).freq + 1 nextPlace = currentPlace.Next() } if nextPlace == nil || nextPlace.Value.(*listEntry).freq != nextFreq { // create a new list entry li := new(listEntry) li.freq = nextFreq li.entries = make(map[*cacheEntry]byte) if currentPlace != nil { nextPlace = c.freqs.InsertAfter(li, currentPlace) } else { nextPlace = c.freqs.PushFront(li) } } e.freqNode = nextPlace nextPlace.Value.(*listEntry).entries[e] = 1 if currentPlace != nil { // remove from current position c.remEntry(currentPlace, e) } } func (c *Cache) remEntry(place *list.Element, entry *cacheEntry) { entries := place.Value.(*listEntry).entries delete(entries, entry) if len(entries) == 0 { c.freqs.Remove(place) } }