241 lines
6.2 KiB
Go
241 lines
6.2 KiB
Go
package candycache
|
||
|
||
import (
|
||
"errors"
|
||
"reflect"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// Структура виде ключ-значение для возвращения списка элементов кэша с их ключами.
|
||
type KeyItemPair struct {
|
||
Key string
|
||
Item Item
|
||
}
|
||
|
||
// Элемент в кэше - это данные и время их жизни.
|
||
type Item struct {
|
||
destroyTimestamp int64 // Момент в Unix-секундах, когда элемент становится устаревшим
|
||
data interface{} // Данные
|
||
}
|
||
|
||
// Кэш - это хранилище элементов и инервал его очистки (ну и мьютекс на всякий случай).
|
||
// Интервал очистки хранилища укахывается в НАНОСЕКУНДАХ (используй множители для преобразования во что-то другое).
|
||
type Cache struct {
|
||
sync.RWMutex // Мьютекс ждя реализации безопасного доступа к общим данным
|
||
storage map[string]Item // Хранилище элементов
|
||
cleanupInterval time.Duration // Интервал очистки хранилища в наносекундах
|
||
}
|
||
|
||
// Создает новый экземпляр Cache с интервалом очистки cleanupInterval.
|
||
// Если cleanupInterval < 0, то кэш не будет очищаться автоматически.
|
||
func Cacher(cleanupInterval time.Duration) *Cache {
|
||
cache := &Cache{
|
||
storage: make(map[string]Item),
|
||
cleanupInterval: cleanupInterval,
|
||
}
|
||
|
||
// Запускаем Garbage Collector если интервал очистки больше 0
|
||
// Иначе (если он отрицательный) кэш будет жить до ручного вызова Cleanup
|
||
if cleanupInterval > 0 {
|
||
go cache.gc(cleanupInterval)
|
||
}
|
||
|
||
return cache
|
||
}
|
||
|
||
// gc = Garbage Collector.
|
||
func (c *Cache) gc(cleanupInterval time.Duration) {
|
||
ticker := time.NewTicker(cleanupInterval)
|
||
defer ticker.Stop()
|
||
|
||
for range ticker.C {
|
||
c.Cleanup()
|
||
}
|
||
}
|
||
|
||
// Перебирает все элементы в кэше, удаляет устаревшие.
|
||
func (c *Cache) Cleanup() {
|
||
c.Lock()
|
||
defer c.Unlock()
|
||
|
||
for key, item := range c.storage {
|
||
if item.destroyTimestamp <= time.Now().UnixNano() {
|
||
delete(c.storage, key)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Удаление всех элементов из кэша.
|
||
func (c *Cache) Flush() {
|
||
c.Lock()
|
||
defer c.Unlock()
|
||
|
||
for key := range c.storage {
|
||
delete(c.storage, key)
|
||
}
|
||
}
|
||
|
||
// Получение элемента из кэша по ключу.
|
||
func (c *Cache) Get(key string) (interface{}, bool) {
|
||
c.RLock()
|
||
defer c.RUnlock()
|
||
|
||
item, found := c.storage[key]
|
||
|
||
// Элемент не найден в кэше
|
||
if !found {
|
||
return nil, false
|
||
}
|
||
|
||
return item.data, true
|
||
}
|
||
|
||
// Определяет является ли элемент устаревшим.
|
||
// Вторым аргументов возвращается есть элемент в кэше или нет.
|
||
// Первым - устаревший элемент или нет.
|
||
func (c *Cache) IsExpired(key string) (bool, bool) {
|
||
c.RLock()
|
||
defer c.RUnlock()
|
||
|
||
item, found := c.storage[key]
|
||
|
||
// Элемент не найден в кэше
|
||
if !found {
|
||
return false, false
|
||
}
|
||
|
||
if item.destroyTimestamp <= time.Now().UnixNano() {
|
||
return true, true
|
||
} else {
|
||
return false, true
|
||
}
|
||
}
|
||
|
||
// Удаление элемента по ключу.
|
||
func (c *Cache) Delete(key string) error {
|
||
c.Lock()
|
||
defer c.Unlock()
|
||
|
||
if _, found := c.storage[key]; !found {
|
||
return errors.New("key not found")
|
||
}
|
||
|
||
delete(c.storage, key)
|
||
|
||
return nil
|
||
}
|
||
|
||
// Добавление элемента в кэш.
|
||
// key - ключ.
|
||
// data - данные.
|
||
// ttl - время жизни элемента (time to life) в наносекундах.
|
||
func (c *Cache) Set(key string, data interface{}, ttl time.Duration) {
|
||
c.Lock()
|
||
defer c.Unlock()
|
||
|
||
c.storage[key] = Item{
|
||
destroyTimestamp: time.Now().UnixNano() + int64(ttl),
|
||
data: data,
|
||
}
|
||
}
|
||
|
||
// Вернет количество элементов в кэше.
|
||
func (c *Cache) Count() int {
|
||
c.RLock()
|
||
defer c.RUnlock()
|
||
|
||
return len(c.storage)
|
||
}
|
||
|
||
// Возвращает список всех элементов кэша.
|
||
func (c *Cache) List() []KeyItemPair {
|
||
c.RLock()
|
||
defer c.RUnlock()
|
||
|
||
items := []KeyItemPair{}
|
||
|
||
for key, item := range c.storage {
|
||
items = append(items, KeyItemPair{Key: key, Item: item})
|
||
}
|
||
|
||
return items
|
||
}
|
||
|
||
// Возвращает список всех устаревших элементов кэша.
|
||
func (c *Cache) ExpiredList() []KeyItemPair {
|
||
c.RLock()
|
||
defer c.RUnlock()
|
||
|
||
items := []KeyItemPair{}
|
||
|
||
for key, item := range c.storage {
|
||
if item.destroyTimestamp <= time.Now().UnixNano() {
|
||
items = append(items, KeyItemPair{Key: key, Item: item})
|
||
}
|
||
}
|
||
|
||
return items
|
||
}
|
||
|
||
// Вернет размер всего кэша в байтах.
|
||
func (c *Cache) Size() int {
|
||
c.RLock()
|
||
defer c.RUnlock()
|
||
|
||
size := 0
|
||
for key, item := range c.storage {
|
||
size += isize(key) + isize(item.data) + isize(item.destroyTimestamp)
|
||
}
|
||
|
||
return size
|
||
}
|
||
|
||
func isize(i interface{}) int {
|
||
if i == nil {
|
||
return 0
|
||
}
|
||
val := reflect.ValueOf(i)
|
||
kind := val.Kind()
|
||
size := 0
|
||
switch kind {
|
||
case reflect.Slice, reflect.Array, reflect.String:
|
||
len := val.Len()
|
||
for i := 0; i < len; i++ {
|
||
size += isize(val.Index(i).Interface())
|
||
}
|
||
return size
|
||
case reflect.Map:
|
||
for _, key := range val.MapKeys() {
|
||
size += isize(key.Interface()) + isize(val.MapIndex(key).Interface())
|
||
}
|
||
return size
|
||
case reflect.Struct:
|
||
for i := 0; i < val.NumField(); i++ {
|
||
size += isize(val.Field(i).Interface())
|
||
}
|
||
return size
|
||
default:
|
||
return int(reflect.TypeOf(i).Size())
|
||
}
|
||
}
|
||
|
||
// Возвращает данные элемента кэша.
|
||
func (i *Item) Data() interface{} {
|
||
return i.data
|
||
}
|
||
|
||
// Возвращает момент смерти элемента кэша.
|
||
func (i *Item) DestroyTimestamp() int64 {
|
||
return i.destroyTimestamp
|
||
}
|
||
|
||
// Определяет является ли элемент устаревшим.
|
||
func (i *Item) IsExpired() bool {
|
||
if i.destroyTimestamp <= time.Now().UnixNano() {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
}
|