-
Notifications
You must be signed in to change notification settings - Fork 126
/
Copy pathcachehash.go
185 lines (169 loc) · 5 KB
/
cachehash.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
/*
* ZDNS Copyright 2022 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package cachehash
import (
"container/list"
"sync"
log "github.com/sirupsen/logrus"
)
// CacheHash is an LRU cache implemented with a hash map and a doubly linked list. The list stores key-value pairs
// in the order they were accessed, with the most recently accessed key-value pair at the front of the list.
// This allows for O(1) insertions, deletions, and lookups and ensures the most recently accesssed elements are
// persisted in the cache.
type CacheHash struct {
sync.Mutex
h map[interface{}]*list.Element
l *list.List
len int
maxLen int
ejectCB func(interface{}, interface{})
}
type keyValue struct {
Key interface{}
Value interface{}
}
// Init initializes the cache with a maximum length.
func (c *CacheHash) Init(maxLen int) {
c.l = list.New()
c.l = c.l.Init()
c.h = make(map[interface{}]*list.Element)
c.len = 0
c.maxLen = maxLen
}
// Eject removes the least-recently used key-value pair from the cache.
func (c *CacheHash) Eject() {
if c.len == 0 {
// nothing to eject
return
}
e := c.l.Back()
kv, ok := e.Value.(keyValue)
if !ok {
log.Panic("CacheHash: Eject: invalid list element value type")
}
if c.ejectCB != nil {
c.ejectCB(kv.Key, kv.Value)
}
delete(c.h, kv.Key)
c.l.Remove(e)
c.len--
}
// Upsert upserts a new key-value pair into the cache.
// If the key already exists, the value is updated and the key is moved to the front of the list.
// If the key does not exist in the cache, the key-value pair is added to the front of the list.
// Returns whether the key already existed in the cache and if the cache had to eject an entry to insert this one.
func (c *CacheHash) Upsert(k interface{}, v interface{}) (didExist, didEject bool) {
didEject = false
var updatedKV keyValue
updatedKV.Key = k
updatedKV.Value = v
e, ok := c.h[k]
if ok {
// update value to have the new value
e.Value = updatedKV
c.l.MoveToFront(e)
return true, didEject
}
if c.len >= c.maxLen {
// cache is full, remove oldest key-value pair
didEject = true
c.Eject()
}
e = c.l.PushFront(updatedKV)
c.len++
c.h[k] = e
return false, didEject
}
// First returns the key-value pair at the front of the list.
// Returns nil, nil if the cache is empty.
func (c *CacheHash) First() (k interface{}, v interface{}) {
if c.len == 0 {
return nil, nil
}
e := c.l.Front()
kv, ok := e.Value.(keyValue)
if !ok {
log.Panic("CacheHash: First: invalid list element value type")
}
return kv.Key, kv.Value
}
// Last returns the key-value pair at the back of the list.
// Returns nil, nil if the cache is empty.
func (c *CacheHash) Last() (k interface{}, v interface{}) {
if c.len == 0 {
return nil, nil
}
e := c.l.Back()
kv, ok := e.Value.(keyValue)
if !ok {
log.Panic("CacheHash: Last: invalid list element value type")
}
return kv.Key, kv.Value
}
// Get returns the value associated with the key and whether the key was found in the cache.
// It also moves it to the front of the list.
// v is nil if the key was not found.
func (c *CacheHash) Get(k interface{}) (v interface{}, found bool) {
e, ok := c.h[k]
if !ok {
return nil, false
}
c.l.MoveToFront(e)
kv, ok := e.Value.(keyValue)
if !ok {
log.Panic("CacheHash: Get: invalid list element value type")
}
return kv.Value, true
}
// GetNoMove returns the value associated with the key and whether the key was found in the cache.
func (c *CacheHash) GetNoMove(k interface{}) (v interface{}, found bool) {
e, ok := c.h[k]
if !ok {
return nil, false
}
kv, ok := e.Value.(keyValue)
if !ok {
log.Panic("CacheHash: GetNoMove: invalid list element value type")
}
return kv.Value, true
}
// Has returns whether the key is in the cache.
func (c *CacheHash) Has(k interface{}) bool {
_, ok := c.h[k]
return ok
}
// Delete removes the key-value pair from the cache and returns the value and whether the key was found.
// v is nil if the key was not found.
func (c *CacheHash) Delete(k interface{}) (v interface{}, found bool) {
e, ok := c.h[k]
if !ok {
return nil, false
}
kv, ok := e.Value.(keyValue)
if !ok {
log.Panic("CacheHash: Delete: invalid list element value type")
}
delete(c.h, k)
c.l.Remove(e)
c.len--
return kv.Value, true
}
// Len returns the number of key-value pairs in the cache.
func (c *CacheHash) Len() int {
return c.len
}
// RegisterCB registers a callback function to be called when an element is ejected from the cache.
func (c *CacheHash) RegisterCB(newCB func(interface{}, interface{})) {
c.ejectCB = newCB
}