123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- package gcache
- import (
- "container/list"
- "time"
- )
- // Discards the least frequently used items first.
- type LFUCache struct {
- baseCache
- items map[interface{}]*lfuItem
- freqList *list.List // list for freqEntry
- }
- func newLFUCache(cb *CacheBuilder) *LFUCache {
- c := &LFUCache{}
- buildCache(&c.baseCache, cb)
- c.init()
- c.loadGroup.cache = c
- return c
- }
- func (c *LFUCache) init() {
- c.freqList = list.New()
- c.items = make(map[interface{}]*lfuItem, c.size+1)
- c.freqList.PushFront(&freqEntry{
- freq: 0,
- items: make(map[*lfuItem]struct{}),
- })
- }
- // Set a new key-value pair
- func (c *LFUCache) Set(key, value interface{}) error {
- c.mu.Lock()
- defer c.mu.Unlock()
- _, err := c.set(key, value)
- return err
- }
- // Set a new key-value pair with an expiration time
- func (c *LFUCache) SetWithExpire(key, value interface{}, expiration time.Duration) error {
- c.mu.Lock()
- defer c.mu.Unlock()
- item, err := c.set(key, value)
- if err != nil {
- return err
- }
- t := c.clock.Now().Add(expiration)
- item.(*lfuItem).expiration = &t
- return nil
- }
- func (c *LFUCache) set(key, value interface{}) (interface{}, error) {
- var err error
- if c.serializeFunc != nil {
- value, err = c.serializeFunc(key, value)
- if err != nil {
- return nil, err
- }
- }
- // Check for existing item
- item, ok := c.items[key]
- if ok {
- item.value = value
- } else {
- // Verify size not exceeded
- if len(c.items) >= c.size {
- c.evict(1)
- }
- item = &lfuItem{
- clock: c.clock,
- key: key,
- value: value,
- freqElement: nil,
- }
- el := c.freqList.Front()
- fe := el.Value.(*freqEntry)
- fe.items[item] = struct{}{}
- item.freqElement = el
- c.items[key] = item
- }
- if c.expiration != nil {
- t := c.clock.Now().Add(*c.expiration)
- item.expiration = &t
- }
- if c.addedFunc != nil {
- c.addedFunc(key, value)
- }
- return item, nil
- }
- // Get a value from cache pool using key if it exists.
- // If it dose not exists key and has LoaderFunc,
- // generate a value using `LoaderFunc` method returns value.
- func (c *LFUCache) Get(key interface{}) (interface{}, error) {
- v, err := c.get(key, false)
- if err == KeyNotFoundError {
- return c.getWithLoader(key, true)
- }
- return v, err
- }
- // Get a value from cache pool using key if it exists.
- // If it dose not exists key, returns KeyNotFoundError.
- // And send a request which refresh value for specified key if cache object has LoaderFunc.
- func (c *LFUCache) GetIFPresent(key interface{}) (interface{}, error) {
- v, err := c.get(key, false)
- if err == KeyNotFoundError {
- return c.getWithLoader(key, false)
- }
- return v, err
- }
- func (c *LFUCache) get(key interface{}, onLoad bool) (interface{}, error) {
- v, err := c.getValue(key, onLoad)
- if err != nil {
- return nil, err
- }
- if c.deserializeFunc != nil {
- return c.deserializeFunc(key, v)
- }
- return v, nil
- }
- func (c *LFUCache) getValue(key interface{}, onLoad bool) (interface{}, error) {
- c.mu.Lock()
- item, ok := c.items[key]
- if ok {
- if !item.IsExpired(nil) {
- c.increment(item)
- v := item.value
- c.mu.Unlock()
- if !onLoad {
- c.stats.IncrHitCount()
- }
- return v, nil
- }
- c.removeItem(item)
- }
- c.mu.Unlock()
- if !onLoad {
- c.stats.IncrMissCount()
- }
- return nil, KeyNotFoundError
- }
- func (c *LFUCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) {
- if c.loaderExpireFunc == nil {
- return nil, KeyNotFoundError
- }
- value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) {
- if e != nil {
- return nil, e
- }
- c.mu.Lock()
- defer c.mu.Unlock()
- item, err := c.set(key, v)
- if err != nil {
- return nil, err
- }
- if expiration != nil {
- t := c.clock.Now().Add(*expiration)
- item.(*lfuItem).expiration = &t
- }
- return v, nil
- }, isWait)
- if err != nil {
- return nil, err
- }
- return value, nil
- }
- func (c *LFUCache) increment(item *lfuItem) {
- currentFreqElement := item.freqElement
- currentFreqEntry := currentFreqElement.Value.(*freqEntry)
- nextFreq := currentFreqEntry.freq + 1
- delete(currentFreqEntry.items, item)
- nextFreqElement := currentFreqElement.Next()
- if nextFreqElement == nil {
- nextFreqElement = c.freqList.InsertAfter(&freqEntry{
- freq: nextFreq,
- items: make(map[*lfuItem]struct{}),
- }, currentFreqElement)
- }
- nextFreqElement.Value.(*freqEntry).items[item] = struct{}{}
- item.freqElement = nextFreqElement
- }
- // evict removes the least frequence item from the cache.
- func (c *LFUCache) evict(count int) {
- entry := c.freqList.Front()
- for i := 0; i < count; {
- if entry == nil {
- return
- } else {
- for item, _ := range entry.Value.(*freqEntry).items {
- if i >= count {
- return
- }
- c.removeItem(item)
- i++
- }
- entry = entry.Next()
- }
- }
- }
- // Removes the provided key from the cache.
- func (c *LFUCache) Remove(key interface{}) bool {
- c.mu.Lock()
- defer c.mu.Unlock()
- return c.remove(key)
- }
- func (c *LFUCache) remove(key interface{}) bool {
- if item, ok := c.items[key]; ok {
- c.removeItem(item)
- return true
- }
- return false
- }
- // removeElement is used to remove a given list element from the cache
- func (c *LFUCache) removeItem(item *lfuItem) {
- delete(c.items, item.key)
- delete(item.freqElement.Value.(*freqEntry).items, item)
- if c.evictedFunc != nil {
- c.evictedFunc(item.key, item.value)
- }
- }
- func (c *LFUCache) keys() []interface{} {
- c.mu.RLock()
- defer c.mu.RUnlock()
- keys := make([]interface{}, len(c.items))
- var i = 0
- for k := range c.items {
- keys[i] = k
- i++
- }
- return keys
- }
- // Returns a slice of the keys in the cache.
- func (c *LFUCache) Keys() []interface{} {
- keys := []interface{}{}
- for _, k := range c.keys() {
- _, err := c.GetIFPresent(k)
- if err == nil {
- keys = append(keys, k)
- }
- }
- return keys
- }
- // Returns all key-value pairs in the cache.
- func (c *LFUCache) GetALL() map[interface{}]interface{} {
- m := make(map[interface{}]interface{})
- for _, k := range c.keys() {
- v, err := c.GetIFPresent(k)
- if err == nil {
- m[k] = v
- }
- }
- return m
- }
- // Returns the number of items in the cache.
- func (c *LFUCache) Len() int {
- return len(c.GetALL())
- }
- // Completely clear the cache
- func (c *LFUCache) Purge() {
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.purgeVisitorFunc != nil {
- for key, item := range c.items {
- c.purgeVisitorFunc(key, item.value)
- }
- }
- c.init()
- }
- type freqEntry struct {
- freq uint
- items map[*lfuItem]struct{}
- }
- type lfuItem struct {
- clock Clock
- key interface{}
- value interface{}
- freqElement *list.Element
- expiration *time.Time
- }
- // returns boolean value whether this item is expired or not.
- func (it *lfuItem) IsExpired(now *time.Time) bool {
- if it.expiration == nil {
- return false
- }
- if now == nil {
- t := it.clock.Now()
- now = &t
- }
- return it.expiration.Before(*now)
- }
|