现在,我们来看下从 CacheTable 里面删除一个 CacheItem 的接口,即 Delete 接口,具体代码如下:
// Delete an item from the cache.
func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) {
table.Lock()
defer table.Unlock()
return table.deleteInternal(key)
}
该接口传入一个我们需要删除的 key,返回被删除的对象,在函数内,我们首先,对整个 table 进行加锁,最后,使用 defer 对整个 table 进行解锁,具体的删除操作,调用的是 deleteInternal 函数,deleteInternal 函数的具体实现如下:
func (table *CacheTable) deleteInternal(key interface{}) (*CacheItem, error) {
r, ok := table.items[key]
if !ok {
return nil, ErrKeyNotFound
}
// Cache value so we don't keep blocking the mutex.
aboutToDeleteItem := table.aboutToDeleteItem
table.Unlock()
// Trigger callbacks before deleting an item from cache.
if aboutToDeleteItem != nil {
aboutToDeleteItem(r)
}
r.RLock()
defer r.RUnlock()
if r.aboutToExpire != nil {
r.aboutToExpire(key)
}
table.Lock()
table.log("Deleting item with key", key, "created on", r.createdOn, "and hit", r.accessCount, "times from table", table.name)
delete(table.items, key)
return r, nil
}
在该函数里,首先,判断要删除的键是否存在,如果键不存在,则直接返回。接着,是同样的操作,先获取要操作的对象,并立即释放锁。也就是,如果 CacheTable 里有删除的回调,那么首先调用删除的回调。
接着,判断 CacheItem 对象是否有删除的回调,如果有,则调用,最后,直接调用 delete 函数删除 map 中的元素即可。
Count 接口,具体代码如下:
func (table *CacheTable) Count() int {
table.RLock()
defer table.RUnlock()
return len(table.items)
}
该接口非常简单,直接返回 map 的长度即可,注意,操作同样需要加锁。
Foreach 接口,用于遍历 CacheTable 里面的每一个 Item,并调用传入的遍历函数,具体代码如下:
// Foreach all items
func (table *CacheTable) Foreach(trans func(key interface{}, item *CacheItem)) {
table.RLock()
defer table.RUnlock()
for k, v := range table.items {
trans(k, v)
}
}
该接口非常简单,操作同样需要加锁。
Exists 接口,用于判断 CacheTable 里面是否存在某个键的 Item,具体代码如下:
// Exists returns whether an item exists in the cache. Unlike the Value method
// Exists neither tries to fetch data via the loadData callback nor does it
// keep the item alive in the cache.
func (table *CacheTable) Exists(key interface{}) bool {
table.RLock()
defer table.RUnlock()
_, ok := table.items[key]
return ok
}
直接使用 map 进行判断即可。
NotFoundAdd 接口用于如果在 CacheTable 里面不存在某个键,则添加,存在,则不添加,具体代码如下:
// NotFoundAdd tests whether an item not found in the cache. Unlike the Exists
// method this also adds data if they key could not be found.
func (table *CacheTable) NotFoundAdd(key interface{}, lifeSpan time.Duration, data interface{}) bool {
table.Lock()
if _, ok := table.items[key]; ok {
table.Unlock()
return false
}
item := NewCacheItem(key, lifeSpan, data)
table.addInternal(item)
return true
}
该函数首先判断键是否存在,如果存在,则直接返回,否则,创建一个新的 CacheItem,然后调用 addInternal 添加进去即可。
Value 接口用于根据 key 返回缓存中对应的 value ,类型为 *CacheItem 。如果 key 存在,则同时更新它的访问计数和最后访问时间;如果不存在,则尝试调用事先注册的回调函数 loadData 初始化数据到缓存表。具体代码如下:
// Value returns an item from the cache and marks it to be kept alive. You can
// pass additional arguments to your DataLoader callback function.
func (table *CacheTable) Value(key interface{}, args ...interface{}) (*CacheItem, error) {
table.RLock()
r, ok := table.items[key]
loadData := table.loadData
table.RUnlock()
if ok {
// Update access counter and timestamp.
r.KeepAlive()
return r, nil
}
// Item doesn't exist in cache. Try and fetch it with a data-loader.
if loadData != nil {
item := loadData(key, args...)
if item != nil {
table.Add(key, item.lifeSpan, item.data)
return item, nil
}
return nil, ErrKeyNotFoundOrLoadable
}
return nil, ErrKeyNotFound
}
同样,首先加锁,获取要操作的数据并缓存,接着,立即解锁。如果键存在,则直接调用 KeepAlive 方法,更新其访问时间,并直接返回获取到的对象。
如果不存在,并且 CacheTable 的 loadData 回调不为空,则直接调用 loadData 回调,loadData 回调的第二个参数是一个可变参数,回调之后,再次将 CacheItem 添加到 CacheTable 中并返回。
Flush 接口用于删除 CacheTable 中的所有的 CacheItem 对象,具体代码如下:
// Flush deletes all items from this cache table.
func (table *CacheTable) Flush() {
table.Lock()
defer table.Unlock()
table.log("Flushing table", table.name)
table.items = make(map[interface{}]*CacheItem)
table.cleanupInterval = 0
if table.cleanupTimer != nil {
table.cleanupTimer.Stop()
}
}
直接将 items 设置为空并且停止定时器即可。
MostAccessed 接口按照访问次数返回 CacheItem 的数组,具体代码如下:
// CacheItemPair maps key to access counter
type CacheItemPair struct {
Key interface{}
AccessCount int64
}
// CacheItemPairList is a slice of CacheIemPairs that implements sort.
// Interface to sort by AccessCount.
type CacheItemPairList []CacheItemPair
func (p CacheItemPairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p CacheItemPairList) Len() int { return len(p) }
func (p CacheItemPairList) Less(i, j int) bool { return p[i].AccessCount > p[j].AccessCount }
// MostAccessed returns the most accessed items in this cache table
func (table *CacheTable) MostAccessed(count int64) []*CacheItem {
table.RLock()
defer table.RUnlock()
p := make(CacheItemPairList, len(table.items))
i := 0
for k, v := range table.items {
p[i] = CacheItemPair{k, v.accessCount}
i++
}
sort.Sort(p)
var r []*CacheItem
c := int64(0)
for _, v := range p {
if c >= count {
break
}
item, ok := table.items[v.Key]
if ok {
r = append(r, item)
}
c++
}
return r
}
我们看到,首先,定义了一个 CacheItemPair 结构体,该结构体包含一个接口类型的 Key 和一个 int64 类型的 AccessCount 访问次数,接着,我们重写了该结构的 Swap、Len 和 Less 方法,用于排序。
接着,我们使用 make 创建了跟 CacheTable 里面的 items 一样长度的一个切片 p,并使用 for range 遍历 CacheTable 的所有的 items,组装成 CacheItemPair 对象,并添加到切片中,最后,直接调用 sort.Sort 传入该切片即可实现排序,因为上面我们已经重新了三个方法。
最后,我们再次遍历 p 切片,并根据 p 切片里的键获取 CacheTable 对象的 items 的具体值即可,并再次组装成新的切片返回。