1
|
|
|
// Package lrucache implements LRU (Least Recently Used) cache |
2
|
|
|
package lrucache |
3
|
|
|
|
4
|
|
|
import ( |
5
|
|
|
"github.com/arazmj/gerdu/dlinklist" |
6
|
|
|
"github.com/arazmj/gerdu/metrics" |
7
|
|
|
"github.com/inhies/go-bytesize" |
8
|
|
|
"sync" |
9
|
|
|
) |
10
|
|
|
|
11
|
|
|
//LRUCache data structure |
12
|
|
|
type LRUCache struct { |
13
|
|
|
sync.RWMutex |
14
|
|
|
cache map[string]*dlinklist.Node |
15
|
|
|
linklist *dlinklist.DLinkedList |
16
|
|
|
capacity bytesize.ByteSize |
17
|
|
|
size bytesize.ByteSize |
18
|
|
|
} |
19
|
|
|
|
20
|
|
|
// NewCache LRUCache constructor |
21
|
|
|
func NewCache(capacity bytesize.ByteSize) *LRUCache { |
22
|
|
|
return &LRUCache{ |
23
|
|
|
cache: map[string]*dlinklist.Node{}, |
24
|
|
|
linklist: dlinklist.NewLinkedList(), |
25
|
|
|
capacity: capacity, |
26
|
|
|
size: 0, |
27
|
|
|
} |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
// Get returns the value for the key |
31
|
|
|
func (c *LRUCache) Get(key string) (value string, ok bool) { |
32
|
|
|
defer c.Unlock() |
33
|
|
|
c.Lock() |
34
|
|
|
if value, ok := c.cache[key]; ok { |
35
|
|
|
metrics.Hits.Inc() |
36
|
|
|
node := value |
37
|
|
|
c.linklist.RemoveNode(node) |
38
|
|
|
c.linklist.AddNode(node) |
39
|
|
|
return node.Value, true |
40
|
|
|
} |
41
|
|
|
metrics.Miss.Inc() |
42
|
|
|
return "", false |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
// Put updates or insert a new entry, evicts the old entry |
46
|
|
|
// if cache size is larger than capacity |
47
|
|
|
func (c *LRUCache) Put(key string, value string) (created bool) { |
48
|
|
|
defer c.Unlock() |
49
|
|
|
c.Lock() |
50
|
|
|
if v, ok := c.cache[key]; ok { |
51
|
|
|
node := v |
52
|
|
|
c.linklist.RemoveNode(node) |
53
|
|
|
c.linklist.AddNode(node) |
54
|
|
|
node.Value = value |
55
|
|
|
created = false |
56
|
|
|
} else { |
57
|
|
|
node := &dlinklist.Node{Key: key, Value: value} |
58
|
|
|
c.linklist.AddNode(node) |
59
|
|
|
c.cache[key] = node |
60
|
|
|
metrics.Adds.Inc() |
61
|
|
|
c.size += bytesize.ByteSize(len(value)) |
62
|
|
|
for c.size > c.capacity { |
63
|
|
|
metrics.Deletes.Inc() |
64
|
|
|
tail := c.linklist.PopTail() |
65
|
|
|
c.size -= bytesize.ByteSize(len(tail.Value)) |
66
|
|
|
delete(c.cache, tail.Key) |
67
|
|
|
} |
68
|
|
|
created = true |
69
|
|
|
} |
70
|
|
|
return created |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
// HasKey indicates the key exists or not |
74
|
|
|
func (c *LRUCache) HasKey(key string) bool { |
75
|
|
|
defer c.RUnlock() |
76
|
|
|
c.RLock() |
77
|
|
|
_, ok := c.cache[key] |
78
|
|
|
return ok |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
//Delete the key from the cache |
82
|
|
|
func (c *LRUCache) Delete(key string) (ok bool) { |
83
|
|
|
if node, ok := c.cache[key]; ok { |
84
|
|
|
metrics.Deletes.Inc() |
85
|
|
|
c.linklist.RemoveNode(node) |
86
|
|
|
delete(c.cache, key) |
87
|
|
|
} else { |
88
|
|
|
return false |
89
|
|
|
} |
90
|
|
|
return true |
91
|
|
|
} |
92
|
|
|
|