blob: c5379dfaf56f2f1d4b9b801971d3fcd39f0d6429 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package lru implements a Least-Recently-Used (LRU) cache of objects keyed by a string.
//
// A given key can have multiple values associated with it in the cache.
package lru
import "container/list"
// Cache implements a Least-Recently-Used cache of objects.
// Cache objects are not safe for concurrent use.
type Cache struct {
m map[string]*list.List // map from cache key to elements in l that are associated with that key.
l *list.List // contents of the cache (as lElem objects), LRU at the back.
capacity int // maximum number of (key, value) pairs to cache.
}
// lElem is the type used for elements in the Cache.l list.
type lElem struct {
key string
value interface{}
}
// New creates an LRU-cache that can hold up to capacity objects.
func New(capacity int) *Cache {
return &Cache{make(map[string]*list.List), list.New(), capacity}
}
// Put adds the provided (key, value) pair to the cache and returns the (key,
// value) pair that was evicted (if any) in order to make space for the
// provided pair.
//
// It is legal to use the same key in multiple Put invocations as the cache can
// hold multiple values associated with the same key.
func (c *Cache) Put(key string, value interface{}) (evictedKey string, evictedValue interface{}, evicted bool) {
if evict := c.l.Len() >= c.capacity; evict {
lElem := c.evict()
evicted = true
evictedKey = lElem.key
evictedValue = lElem.value
}
ml, ok := c.m[key]
if !ok {
ml = list.New()
c.m[key] = ml
}
// Now add to c.l and ml
ml.PushFront(c.l.PushFront(lElem{key, value}))
return
}
// Get returns a value in the cache associated with the provided key and
// removes it from the cache's storage. It returns (nil, false) if no value is
// associated with the key.
//
// If there are multiple values associated with the provided key, Get will
// return the most recently used one.
func (c *Cache) Get(key string) (interface{}, bool) {
ml, ok := c.m[key]
if !ok {
return nil, false
}
lElem := c.l.Remove(ml.Remove(ml.Front()).(*list.Element)).(lElem)
// At this point, lElem.key == key
// if lElem.key != key { panic("Bug! Cache.m[key] is not in sync with Cacke.l") }
if ml.Len() == 0 {
delete(c.m, key)
}
return lElem.value, true
}
func (c *Cache) evict() lElem {
lElem := c.l.Remove(c.l.Back()).(lElem)
// Remove the entry in c.m[key].
ml := c.m[lElem.key]
ml.Remove(ml.Back())
if ml.Len() == 0 {
delete(c.m, lElem.key)
}
return lElem
}
// Size returns the number of items currently in the cache.
func (c *Cache) Size() int {
return c.l.Len()
}