github.com/vicanso/lru-ttl@v1.5.1/lru_ttl.go (about) 1 // Copyright 2020 tree xie 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package lruttl 16 17 import ( 18 "errors" 19 "time" 20 21 lru "github.com/hashicorp/golang-lru" 22 ) 23 24 type Key interface{} 25 26 type Cache struct { 27 ttl time.Duration 28 lru *lru.Cache 29 onEvicted func(key Key, value interface{}) 30 } 31 32 // CacheOption cache option 33 type CacheOption func(c *Cache) 34 35 type cacheItem struct { 36 expiredAt int64 37 value interface{} 38 } 39 40 func (item *cacheItem) isExpired() bool { 41 return item.expiredAt < time.Now().UnixNano() 42 } 43 44 // New returns a new lru cache with ttl 45 func New(maxEntries int, defaultTTL time.Duration, opts ...CacheOption) *Cache { 46 if maxEntries <= 0 || defaultTTL <= 0 { 47 panic(errors.New("maxEntries and default ttl must be gt 0")) 48 } 49 c := &Cache{ 50 ttl: defaultTTL, 51 } 52 for _, opt := range opts { 53 opt(c) 54 } 55 var fn func(key, value interface{}) 56 // 如果有设置on evicted 57 if c.onEvicted != nil { 58 fn = func(key, value interface{}) { 59 c.onEvicted(key, value) 60 } 61 } 62 63 l, err := lru.NewWithEvict(maxEntries, fn) 64 // lru 缓存全局初始化,因此直接panic 65 // 除了长度少于0,其它情况不会出错 66 if err != nil { 67 panic(err) 68 } 69 c.lru = l 70 71 return c 72 73 } 74 75 // CacheEvictedOption sets evicted function to cache 76 func CacheEvictedOption(fn func(key Key, value interface{})) CacheOption { 77 return func(c *Cache) { 78 c.onEvicted = fn 79 } 80 } 81 82 // Add adds a value to the cache, it will use default ttl if the ttl is nil. 83 func (c *Cache) Add(key Key, value interface{}, ttl ...time.Duration) { 84 expiredAt := time.Now().UnixNano() 85 if len(ttl) != 0 { 86 expiredAt += ttl[0].Nanoseconds() 87 } else { 88 expiredAt += c.ttl.Nanoseconds() 89 } 90 c.lru.Add(key, &cacheItem{ 91 expiredAt: expiredAt, 92 value: value, 93 }) 94 } 95 96 // Get returns value and exists from the cache by key, if value is expired then remove it. 97 // If the value is expired, value is not nil but exists is false. 98 func (c *Cache) Get(key Key) (interface{}, bool) { 99 data, ok := c.lru.Get(key) 100 if !ok { 101 return nil, false 102 } 103 item, ok := data.(*cacheItem) 104 if !ok { 105 return nil, false 106 } 107 // 过期的元素数据也返回,但ok为false 108 // 由于未做并发控制,因此有可能并发时导致数据被清除(另外一个goroutine刚好在更新) 109 // 由于是缓存数据并不会导致数据出错,因此不添加并发控制 110 value := item.value 111 if item.isExpired() { 112 // 过期的元素删除 113 c.lru.Remove(key) 114 return value, false 115 } 116 return value, true 117 } 118 119 // GetBytes is the same as Get function, but returns []byte 120 func (c *Cache) GetBytes(key Key) ([]byte, bool) { 121 value, ok := c.Get(key) 122 var buf []byte 123 if value != nil { 124 buf, _ = value.([]byte) 125 } 126 return buf, ok 127 } 128 129 // TTL returns the ttl of key 130 func (c *Cache) TTL(key Key) time.Duration { 131 data, ok := c.lru.Peek(key) 132 if !ok { 133 // 元素不存在 134 return time.Duration(-2) 135 } 136 item, ok := data.(*cacheItem) 137 if !ok { 138 // 元素转换失败则认为不存在 139 return time.Duration(-2) 140 } 141 now := time.Now().UnixNano() 142 if item.expiredAt <= now { 143 // 元素已过期 144 return time.Duration(-1) 145 } 146 return time.Duration(item.expiredAt - now) 147 } 148 149 // Peek get a key's value from the cache, but not move to front. 150 // The performance is better than get. 151 // It will not be removed if the cache is expired. 152 func (c *Cache) Peek(key Key) (interface{}, bool) { 153 data, ok := c.lru.Peek(key) 154 if !ok { 155 return nil, false 156 } 157 item, ok := data.(*cacheItem) 158 if !ok { 159 return nil, false 160 } 161 // 过期的元素数据也返回,但ok为false 162 value := item.value 163 if item.isExpired() { 164 // 过期不清除 165 return value, false 166 } 167 return value, true 168 } 169 170 // PeekBytes is the same as Peek function, but returns []byte 171 func (c *Cache) PeekBytes(key Key) ([]byte, bool) { 172 value, ok := c.Peek(key) 173 var buf []byte 174 if value != nil { 175 buf, _ = value.([]byte) 176 } 177 return buf, ok 178 } 179 180 // Remove removes the key's value from the cache. 181 func (c *Cache) Remove(key Key) { 182 c.lru.Remove(key) 183 } 184 185 // Len returns the number of items in the cache. 186 func (c *Cache) Len() int { 187 return c.lru.Len() 188 } 189 190 // Keys gets all keys of cache 191 func (c *Cache) Keys() []Key { 192 keys := c.lru.Keys() 193 result := make([]Key, len(keys)) 194 for i, k := range keys { 195 result[i] = k 196 } 197 return result 198 }