-
Notifications
You must be signed in to change notification settings - Fork 21
/
lru.go
216 lines (194 loc) · 5.1 KB
/
lru.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright 2020 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package tinylru
import "sync"
// DefaultSize is the default maximum size of an LRU cache before older items
// get automatically evicted.
const DefaultSize = 256
type lruItem struct {
key interface{} // user-defined key
value interface{} // user-defined value
prev *lruItem // prev item in list. More recently used
next *lruItem // next item in list. Less recently used
}
// LRU implements an LRU cache
type LRU struct {
mu sync.RWMutex // protect all things
size int // max number of items.
items map[interface{}]*lruItem // active items
head *lruItem // head of list
tail *lruItem // tail of list
}
//go:noinline
func (lru *LRU) init() {
lru.items = make(map[interface{}]*lruItem)
lru.head = new(lruItem)
lru.tail = new(lruItem)
lru.head.next = lru.tail
lru.tail.prev = lru.head
if lru.size == 0 {
lru.size = DefaultSize
}
}
func (lru *LRU) evict() *lruItem {
item := lru.tail.prev
lru.pop(item)
delete(lru.items, item.key)
return item
}
func (lru *LRU) pop(item *lruItem) {
item.prev.next = item.next
item.next.prev = item.prev
}
func (lru *LRU) push(item *lruItem) {
lru.head.next.prev = item
item.next = lru.head.next
item.prev = lru.head
lru.head.next = item
}
// Resize sets the maximum size of an LRU cache. If this value is less than
// the number of items currently in the cache, then items will be evicted.
// Returns evicted items.
// This operation will panic if the size is less than one.
func (lru *LRU) Resize(size int) (evictedKeys []interface{},
evictedValues []interface{}) {
if size <= 0 {
panic("invalid size")
}
lru.mu.Lock()
defer lru.mu.Unlock()
for size < len(lru.items) {
item := lru.evict()
evictedKeys = append(evictedKeys, item.key)
evictedValues = append(evictedValues, item.value)
}
lru.size = size
return evictedKeys, evictedValues
}
// Len returns the length of the lru cache
func (lru *LRU) Len() int {
lru.mu.RLock()
defer lru.mu.RUnlock()
return len(lru.items)
}
// SetEvicted sets or replaces a value for a key. If this operation causes an
// eviction then the evicted item is returned.
func (lru *LRU) SetEvicted(key interface{}, value interface{}) (
prev interface{}, replaced bool, evictedKey interface{},
evictedValue interface{}, evicted bool) {
lru.mu.Lock()
defer lru.mu.Unlock()
if lru.items == nil {
lru.init()
}
item := lru.items[key]
if item == nil {
if len(lru.items) == lru.size {
item = lru.evict()
evictedKey, evictedValue, evicted = item.key, item.value, true
} else {
item = new(lruItem)
}
item.key = key
item.value = value
lru.push(item)
lru.items[key] = item
} else {
prev, replaced = item.value, true
item.value = value
if lru.head.next != item {
lru.pop(item)
lru.push(item)
}
}
return prev, replaced, evictedKey, evictedValue, evicted
}
// Set or replace a value for a key.
func (lru *LRU) Set(key interface{}, value interface{}) (prev interface{},
replaced bool) {
prev, replaced, _, _, _ = lru.SetEvicted(key, value)
return prev, replaced
}
// Get a value for key
func (lru *LRU) Get(key interface{}) (value interface{}, ok bool) {
lru.mu.Lock()
defer lru.mu.Unlock()
item := lru.items[key]
if item == nil {
return nil, false
}
if lru.head.next != item {
lru.pop(item)
lru.push(item)
}
return item.value, true
}
// Contains returns true if the key exists.
func (lru *LRU) Contains(key interface{}) bool {
lru.mu.RLock()
defer lru.mu.RUnlock()
_, ok := lru.items[key]
return ok
}
// Peek returns the value for key value without updating
// the recently used status.
func (lru *LRU) Peek(key interface{}) (value interface{}, ok bool) {
lru.mu.RLock()
defer lru.mu.RUnlock()
if item := lru.items[key]; item != nil {
return item.value, true
}
return nil, false
}
// Delete a value for a key
func (lru *LRU) Delete(key interface{}) (prev interface{}, deleted bool) {
lru.mu.Lock()
defer lru.mu.Unlock()
item := lru.items[key]
if item == nil {
return nil, false
}
delete(lru.items, key)
lru.pop(item)
return item.value, true
}
// Range iterates over all key/values in the order of most recently to
// least recently used items.
func (lru *LRU) Range(iter func(key interface{}, value interface{}) bool) {
lru.mu.RLock()
defer lru.mu.RUnlock()
if head := lru.head; head != nil {
item := head.next
for item != lru.tail {
if !iter(item.key, item.value) {
return
}
item = item.next
}
}
}
// Reverse iterates over all key/values in the order of least recently to
// most recently used items.
func (lru *LRU) Reverse(iter func(key interface{}, value interface{}) bool) {
lru.mu.RLock()
defer lru.mu.RUnlock()
if tail := lru.tail; tail != nil {
item := tail.prev
for item != lru.head {
if !iter(item.key, item.value) {
return
}
item = item.prev
}
}
}
// Clear will remove all key/values from the LRU cache
func (lru *LRU) Clear() {
lru.mu.Lock()
defer lru.mu.Unlock()
lru.size = 0
lru.items = nil
lru.head = nil
lru.tail = nil
}