candycache/candycache.go

319 lines
7.6 KiB
Go
Raw Normal View History

2025-01-06 00:14:48 +03:00
package candycache
2025-01-02 23:30:51 +03:00
import (
"encoding/json"
2025-01-02 23:30:51 +03:00
"errors"
"fmt"
"io"
"reflect"
2025-01-02 23:30:51 +03:00
"sync"
"time"
)
// Структура виде ключ-значение для возвращения списка элементов кэша с их ключами.
type KeyItemPair struct {
Key string
Item Item
}
2025-01-02 23:30:51 +03:00
// Элемент в кэше - это данные и время их жизни.
type Item struct {
destroyTimestamp int64 // Момент в Unix-секундах, когда элемент становится устаревшим
data interface{} // Данные
}
// Кэш - это хранилище элементов и инервал его очистки (ну и мьютекс на всякий случай).
// Интервал очистки хранилища укахывается в НАНОСЕКУНДАХ (используй множители для преобразования во что-то другое).
type Cache struct {
sync.RWMutex // Мьютекс ждя реализации безопасного доступа к общим данным
storage map[string]Item // Хранилище элементов
cleanupInterval time.Duration // Интервал очистки хранилища в наносекундах
}
2025-01-06 00:16:20 +03:00
// Создает новый экземпляр Cache с интервалом очистки cleanupInterval.
2025-01-02 23:30:51 +03:00
// Если cleanupInterval < 0, то кэш не будет очищаться автоматически.
2025-01-06 00:14:48 +03:00
func Cacher(cleanupInterval time.Duration) *Cache {
2025-01-02 23:30:51 +03:00
cache := &Cache{
storage: make(map[string]Item),
cleanupInterval: cleanupInterval,
}
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() {
2025-01-02 23:30:51 +03:00
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{}, error) {
2025-01-02 23:30:51 +03:00
c.RLock()
defer c.RUnlock()
item, found := c.storage[key]
if !found {
return nil, errors.New("key not found")
2025-01-02 23:30:51 +03:00
}
return item.data, nil
2025-01-02 23:30:51 +03:00
}
// Определяет является ли элемент устаревшим.
// Вторым аргументов возвращается есть элемент в кэше или нет.
// Первым - устаревший элемент или нет.
func (c *Cache) IsExpired(key string) (bool, error) {
c.RLock()
defer c.RUnlock()
item, found := c.storage[key]
if !found {
return false, errors.New("key not found")
}
if item.destroyTimestamp <= time.Now().UnixNano() {
return true, nil
} else {
return false, nil
}
}
2025-01-02 23:30:51 +03:00
// Удаление элемента по ключу.
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) в наносекундах.
2025-01-06 12:27:28 +03:00
func (c *Cache) Set(key string, data interface{}, ttl time.Duration) {
2025-01-02 23:30:51 +03:00
c.Lock()
defer c.Unlock()
c.storage[key] = Item{
destroyTimestamp: time.Now().UnixNano() + int64(ttl),
2025-01-02 23:30:51 +03:00
data: data,
}
}
// Вернет количество элементов в кэше.
func (c *Cache) Count() int {
c.RLock()
defer c.RUnlock()
return len(c.storage)
}
// Возвращает список всех элементов кэша.
func (c *Cache) List() []KeyItemPair {
2025-01-02 23:30:51 +03:00
c.RLock()
defer c.RUnlock()
items := []KeyItemPair{}
2025-01-02 23:30:51 +03:00
for key, item := range c.storage {
items = append(items, KeyItemPair{Key: key, Item: item})
2025-01-02 23:30:51 +03:00
}
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
}
// Save сохраняет кэш в io.Writer в формате JSON, записывая каждый элемент по отдельности.
func (c *Cache) Save(w io.Writer) error {
c.RLock()
defer c.RUnlock()
if _, err := w.Write([]byte("[\n")); err != nil {
return err
}
encoder := json.NewEncoder(w)
first := true
for key, item := range c.storage {
entry := struct {
Key string `json:"key"`
DestroyTimestamp int64 `json:"destroyTimestamp"`
Data interface{} `json:"data"`
}{
Key: key,
DestroyTimestamp: item.destroyTimestamp,
Data: item.data,
}
if !first {
if _, err := w.Write([]byte(",\n")); err != nil {
return err
}
}
first = false
if err := encoder.Encode(entry); err != nil {
return err
}
}
if _, err := w.Write([]byte("]")); err != nil {
return err
}
return nil
}
// Load загружает кэш из io.Reader в формате JSON.
func (c *Cache) Load(r io.Reader) error {
c.Lock()
defer c.Unlock()
decoder := json.NewDecoder(r)
if _, err := decoder.Token(); err != nil {
if err == io.EOF {
return fmt.Errorf("файл пустой или имеет неправильный формат")
}
return err
}
for decoder.More() {
var entry struct {
Key string `json:"key"`
DestroyTimestamp int64 `json:"destroyTimestamp"`
Data interface{} `json:"data"`
}
if err := decoder.Decode(&entry); err != nil {
return err
}
c.storage[entry.Key] = Item{
destroyTimestamp: entry.DestroyTimestamp,
data: entry.Data,
}
}
if _, err := decoder.Token(); err != nil {
return err
}
return nil
}
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
2025-01-02 23:30:51 +03:00
}
// Определяет является ли элемент устаревшим.
func (i *Item) IsExpired() bool {
if i.destroyTimestamp <= time.Now().UnixNano() {
return true
} else {
return false
}
}