Skip to content

Commit 94caee0

Browse files
committed
✨ cache
1 parent e04d952 commit 94caee0

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

cache/cache.go

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package cache
2+
3+
import (
4+
"container/heap"
5+
"sync"
6+
"time"
7+
)
8+
9+
type Item struct {
10+
key string
11+
value any
12+
expiration int64
13+
index int // add index for heap.Interface
14+
}
15+
16+
type Cache struct {
17+
items map[string]*Item
18+
defaultExpiration time.Duration
19+
cleanupInterval time.Duration
20+
mu sync.RWMutex
21+
expHeap ExpirationHeap // use ExpirationHeap instead of heap.MinHeap
22+
}
23+
24+
type ExpirationHeap []*Item
25+
26+
var itemPool = sync.Pool{
27+
New: func() any {
28+
return &Item{}
29+
},
30+
}
31+
32+
func (eh ExpirationHeap) Len() int {
33+
return len(eh)
34+
}
35+
36+
func (eh ExpirationHeap) Less(i, j int) bool {
37+
return eh[i].expiration < eh[j].expiration
38+
}
39+
40+
func (eh ExpirationHeap) Swap(i, j int) {
41+
eh[i], eh[j] = eh[j], eh[i]
42+
eh[i].index = i
43+
eh[j].index = j
44+
}
45+
46+
func (eh *ExpirationHeap) Push(x any) {
47+
n := len(*eh)
48+
item := x.(*Item)
49+
item.index = n
50+
*eh = append(*eh, item)
51+
}
52+
53+
func (eh *ExpirationHeap) Pop() any {
54+
old := *eh
55+
n := len(old)
56+
item := old[n-1]
57+
old[n-1] = nil // avoid memory leak
58+
item.index = -1 // for safety
59+
*eh = old[0 : n-1]
60+
return item
61+
}
62+
63+
func NewCache(defaultExpiration, cleanupInterval time.Duration) *Cache {
64+
items := make(map[string]*Item)
65+
expHeap := make(ExpirationHeap, 0)
66+
cache := &Cache{
67+
items: items,
68+
defaultExpiration: defaultExpiration,
69+
cleanupInterval: cleanupInterval,
70+
expHeap: expHeap,
71+
}
72+
heap.Init(&cache.expHeap) // initialize the heap
73+
go cache.cleanupExpired()
74+
return cache
75+
}
76+
77+
func (c *Cache) Set(key string, value any, expiration time.Duration) {
78+
item := itemPool.Get().(*Item)
79+
item.value = value
80+
item.key = key
81+
now := time.Now().UnixNano()
82+
if expiration == 0 {
83+
item.expiration = now + int64(c.defaultExpiration)
84+
} else {
85+
item.expiration = now + int64(expiration)
86+
}
87+
c.mu.Lock()
88+
_, found := c.items[key]
89+
if found {
90+
heap.Remove(&c.expHeap, item.index)
91+
}
92+
c.items[key] = item
93+
heap.Push(&c.expHeap, item)
94+
c.mu.Unlock()
95+
}
96+
97+
func (c *Cache) Get(key string) (any, bool) {
98+
c.mu.RLock()
99+
item, ok := c.items[key]
100+
c.mu.RUnlock()
101+
if !ok || time.Now().UnixNano() < item.expiration {
102+
return nil, false
103+
}
104+
return item.value, true
105+
}
106+
107+
func (c *Cache) cleanupExpired() {
108+
for {
109+
time.Sleep(c.cleanupInterval)
110+
111+
c.mu.Lock()
112+
for c.expHeap.Len() > 0 {
113+
item := heap.Pop(&c.expHeap).(*Item)
114+
if item.expiration > time.Now().UnixNano() {
115+
heap.Push(&c.expHeap, item) // 将未过期的item放回heap
116+
break // 如果最早过期的item都未过期,则退出循环
117+
}
118+
delete(c.items, item.key)
119+
itemPool.Put(item)
120+
}
121+
c.mu.Unlock()
122+
}
123+
}

0 commit comments

Comments
 (0)