github.com/dubbogo/gost@v1.14.0/container/gxlru/lru_cache.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  /*
    18   * Licensed to the Apache Software Foundation (ASF) under one or more
    19   * contributor license agreements.  See the NOTICE file distributed with
    20   * this work for additional information regarding copyright ownership.
    21   * The ASF licenses this file to You under the Apache License, Version 2.0
    22   * (the "License"); you may not use this file except in compliance with
    23   * the License.  You may obtain a copy of the License at
    24   *
    25   *     http://www.apache.org/licenses/LICENSE-2.0
    26   *
    27   * Unless required by applicable law or agreed to in writing, software
    28   * distributed under the License is distributed on an "AS IS" BASIS,
    29   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    30   * See the License for the specific language governing permissions and
    31   * limitations under the License.
    32   */
    33  
    34  // Package cache implements a LRU cache.
    35  //
    36  // The implementation borrows heavily from SmallLRUCache
    37  // (originally by Nathan Schrenk). The object maintains a doubly-linked list of
    38  // elements. When an element is accessed, it is promoted to the head of the
    39  // list. When space is needed, the element at the tail of the list
    40  // (the least recently used element) is evicted.
    41  package gxlru
    42  
    43  import (
    44  	"container/list"
    45  	"fmt"
    46  	"sync"
    47  	"time"
    48  )
    49  
    50  // LRUCache is a typical LRU cache implementation.  If the cache
    51  // reaches the capacity, the least recently used item is deleted from
    52  // the cache. Note the capacity is not the number of items, but the
    53  // total sum of the Size() of each item.
    54  type LRUCache struct {
    55  	mu sync.Mutex
    56  
    57  	// list & table contain *entry objects.
    58  	list  *list.List
    59  	table map[string]*list.Element
    60  
    61  	size      int64
    62  	capacity  int64
    63  	evictions int64
    64  }
    65  
    66  // Value is the interface values that go into LRUCache need to satisfy
    67  type Value interface {
    68  	// Size returns how big this value is. If you want to just track
    69  	// the cache by number of objects, you may return the size as 1.
    70  	Size() int
    71  }
    72  
    73  // Item is what is stored in the cache
    74  type Item struct {
    75  	Key   string
    76  	Value Value
    77  }
    78  
    79  type entry struct {
    80  	key          string
    81  	value        Value
    82  	size         int64
    83  	timeAccessed time.Time
    84  }
    85  
    86  // NewLRUCache creates a new empty cache with the given capacity.
    87  func NewLRUCache(capacity int64) *LRUCache {
    88  	return &LRUCache{
    89  		list:     list.New(),
    90  		table:    make(map[string]*list.Element),
    91  		capacity: capacity,
    92  	}
    93  }
    94  
    95  // Get returns a value from the cache, and marks the entry as most
    96  // recently used.
    97  func (lru *LRUCache) Get(key string) (v Value, ok bool) {
    98  	lru.mu.Lock()
    99  	defer lru.mu.Unlock()
   100  
   101  	element := lru.table[key]
   102  	if element == nil {
   103  		return nil, false
   104  	}
   105  	lru.moveToFront(element)
   106  	return element.Value.(*entry).value, true
   107  }
   108  
   109  // Peek returns a value from the cache without changing the LRU order.
   110  func (lru *LRUCache) Peek(key string) (v Value, ok bool) {
   111  	lru.mu.Lock()
   112  	defer lru.mu.Unlock()
   113  
   114  	element := lru.table[key]
   115  	if element == nil {
   116  		return nil, false
   117  	}
   118  	return element.Value.(*entry).value, true
   119  }
   120  
   121  // Set sets a value in the cache.
   122  func (lru *LRUCache) Set(key string, value Value) {
   123  	lru.mu.Lock()
   124  	defer lru.mu.Unlock()
   125  
   126  	if element := lru.table[key]; element != nil {
   127  		lru.updateInplace(element, value)
   128  	} else {
   129  		lru.addNew(key, value)
   130  	}
   131  }
   132  
   133  // SetIfAbsent will set the value in the cache if not present. If the
   134  // value exists in the cache, we don't set it.
   135  func (lru *LRUCache) SetIfAbsent(key string, value Value) {
   136  	lru.mu.Lock()
   137  	defer lru.mu.Unlock()
   138  
   139  	if element := lru.table[key]; element != nil {
   140  		lru.moveToFront(element)
   141  	} else {
   142  		lru.addNew(key, value)
   143  	}
   144  }
   145  
   146  // Delete removes an entry from the cache, and returns if the entry existed.
   147  func (lru *LRUCache) Delete(key string) bool {
   148  	lru.mu.Lock()
   149  	defer lru.mu.Unlock()
   150  
   151  	element := lru.table[key]
   152  	if element == nil {
   153  		return false
   154  	}
   155  
   156  	lru.list.Remove(element)
   157  	delete(lru.table, key)
   158  	lru.size -= element.Value.(*entry).size
   159  	return true
   160  }
   161  
   162  // Clear will clear the entire cache.
   163  func (lru *LRUCache) Clear() {
   164  	lru.mu.Lock()
   165  	defer lru.mu.Unlock()
   166  
   167  	lru.list.Init()
   168  	lru.table = make(map[string]*list.Element)
   169  	lru.size = 0
   170  }
   171  
   172  // SetCapacity will set the capacity of the cache. If the capacity is
   173  // smaller, and the current cache size exceed that capacity, the cache
   174  // will be shrank.
   175  func (lru *LRUCache) SetCapacity(capacity int64) {
   176  	lru.mu.Lock()
   177  	defer lru.mu.Unlock()
   178  
   179  	lru.capacity = capacity
   180  	lru.checkCapacity()
   181  }
   182  
   183  // Stats returns a few stats on the cache.
   184  func (lru *LRUCache) Stats() (length, size, capacity, evictions int64, oldest time.Time) {
   185  	lru.mu.Lock()
   186  	defer lru.mu.Unlock()
   187  	if lastElem := lru.list.Back(); lastElem != nil {
   188  		oldest = lastElem.Value.(*entry).timeAccessed
   189  	}
   190  	return int64(lru.list.Len()), lru.size, lru.capacity, lru.evictions, oldest
   191  }
   192  
   193  // StatsJSON returns stats as a JSON object in a string.
   194  func (lru *LRUCache) StatsJSON() string {
   195  	if lru == nil {
   196  		return "{}"
   197  	}
   198  	l, s, c, e, o := lru.Stats()
   199  	return fmt.Sprintf("{\"Length\": %v, \"Size\": %v, \"Capacity\": %v, \"Evictions\": %v, \"OldestAccess\": \"%v\"}", l, s, c, e, o)
   200  }
   201  
   202  // Length returns how many elements are in the cache
   203  func (lru *LRUCache) Length() int64 {
   204  	lru.mu.Lock()
   205  	defer lru.mu.Unlock()
   206  	return int64(lru.list.Len())
   207  }
   208  
   209  // Size returns the sum of the objects' Size() method.
   210  func (lru *LRUCache) Size() int64 {
   211  	lru.mu.Lock()
   212  	defer lru.mu.Unlock()
   213  	return lru.size
   214  }
   215  
   216  // Capacity returns the cache maximum capacity.
   217  func (lru *LRUCache) Capacity() int64 {
   218  	lru.mu.Lock()
   219  	defer lru.mu.Unlock()
   220  	return lru.capacity
   221  }
   222  
   223  // Evictions returns the eviction count.
   224  func (lru *LRUCache) Evictions() int64 {
   225  	lru.mu.Lock()
   226  	defer lru.mu.Unlock()
   227  	return lru.evictions
   228  }
   229  
   230  // Oldest returns the insertion time of the oldest element in the cache,
   231  // or a IsZero() time if cache is empty.
   232  func (lru *LRUCache) Oldest() (oldest time.Time) {
   233  	lru.mu.Lock()
   234  	defer lru.mu.Unlock()
   235  	if lastElem := lru.list.Back(); lastElem != nil {
   236  		oldest = lastElem.Value.(*entry).timeAccessed
   237  	}
   238  	return
   239  }
   240  
   241  // Keys returns all the keys for the cache, ordered from most recently
   242  // used to least recently used.
   243  func (lru *LRUCache) Keys() []string {
   244  	lru.mu.Lock()
   245  	defer lru.mu.Unlock()
   246  
   247  	keys := make([]string, 0, lru.list.Len())
   248  	for e := lru.list.Front(); e != nil; e = e.Next() {
   249  		keys = append(keys, e.Value.(*entry).key)
   250  	}
   251  	return keys
   252  }
   253  
   254  // Items returns all the values for the cache, ordered from most recently
   255  // used to least recently used.
   256  func (lru *LRUCache) Items() []Item {
   257  	lru.mu.Lock()
   258  	defer lru.mu.Unlock()
   259  
   260  	items := make([]Item, 0, lru.list.Len())
   261  	for e := lru.list.Front(); e != nil; e = e.Next() {
   262  		v := e.Value.(*entry)
   263  		items = append(items, Item{Key: v.key, Value: v.value})
   264  	}
   265  	return items
   266  }
   267  
   268  func (lru *LRUCache) updateInplace(element *list.Element, value Value) {
   269  	valueSize := int64(value.Size())
   270  	sizeDiff := valueSize - element.Value.(*entry).size
   271  	element.Value.(*entry).value = value
   272  	element.Value.(*entry).size = valueSize
   273  	lru.size += sizeDiff
   274  	lru.moveToFront(element)
   275  	lru.checkCapacity()
   276  }
   277  
   278  func (lru *LRUCache) moveToFront(element *list.Element) {
   279  	lru.list.MoveToFront(element)
   280  	element.Value.(*entry).timeAccessed = time.Now()
   281  }
   282  
   283  func (lru *LRUCache) addNew(key string, value Value) {
   284  	newEntry := &entry{key, value, int64(value.Size()), time.Now()}
   285  	element := lru.list.PushFront(newEntry)
   286  	lru.table[key] = element
   287  	lru.size += newEntry.size
   288  	lru.checkCapacity()
   289  }
   290  
   291  func (lru *LRUCache) checkCapacity() {
   292  	// Partially duplicated from Delete
   293  	for lru.size > lru.capacity {
   294  		delElem := lru.list.Back()
   295  		delValue := delElem.Value.(*entry)
   296  		lru.list.Remove(delElem)
   297  		delete(lru.table, delValue.key)
   298  		lru.size -= delValue.size
   299  		lru.evictions++
   300  	}
   301  }