Compare commits

..

No commits in common. "main" and "v4.0.0" have entirely different histories.
main ... v4.0.0

2 changed files with 39 additions and 368 deletions

235
README.md
View File

@ -1,235 +0,0 @@
# Candy Cache
**CandyCache** — это простой и эффективный кэш на языке Go, который позволяет хранить данные с ограниченным временем жизни (**TTL**).
# Установка
Для использования CandyCache в вашем проекте, установите его, используя ```go get git.hikan.ru/serr/candycache```, далее просто добавьте ```git.hikan.ru/serr/candycache``` в блок импорта.
# Основные возможности
- Автоматическая очистка устаревших элементов и возможность ее отключения.
- Кэшем можно управлять вручную.
- Конкурентный доступ к данным возможен благодаря мьютексам.
- Кэш может хранить данные любых типов.
- Можно создавать и загружать дампы.
# Использование
## Создание кэша
### С автоматической очисткой
Для создания нового экземпляра кэша используйте функцию **Cacher**, передавая интервал очистки в наносекундах.
Если требуется указать интервал в секундах/минутах/часах и т.д. - используйте множители из пакета **time**:
```go
cache := candycache.Cacher(10 * time.Minute) // Очистка каждые 10 минут
```
### Без автоматической очистки
Если автоматичская очистка не нужна - просто передайте параметром любое отрицательное число:
```go
cache := candycache.Cacher(-1) // Кэш не будет очищаться автоматически
```
## Добавление элемента
Для добавления элемента в кэш используйте метод **Set**:
```go
cache.Set("key", "value", 5 * time.Minute) // Элемент будет считаться устаревшим через 5 минут
```
В случае, если по указанном ключу уже что-то хранится, оно будет заменено на новый элемент.
## Получение элемента
Для получения элемента из кэша используйте метод **Get**:
```go
value, err := cache.Get("key") // Получение значения по ключу "key"
```
Если элемент найден, то в переменную **value** будет записано значение, а в **err** - **nil**. Если элемент не найден, то в **err** будет записано **key not found**, а значением вернется **nil**.
## Удаление элемента
Для удаления элемента по ключу используйте метод **Delete**:
```go
err := cache.Delete("key1")
if err != nil {
fmt.Println("Ошибка:", err) // Не найдено записи по ключу
}
```
Элемент будет удален, не смотря на то, устаревший он или нет.
## Массовое удаление элементов
### Удаление устаревших элементов
Для удаления устаревших элементов используйте метод **Cleanup**:
```go
cache.Cleanup() // Перебирает все элементы кэша, удаляет устаревшие
```
### Удаление всех элементов кэша
Для полной очистки кэша используйте метод **Flush**:
```go
cache.Flush() // Удаляет все элементы кэша, не смотря на то, устаревшие они или нет
```
## Получение информации о кэше
### Получение списка элементов
Для получения списка всех элементов в кэше используйте метод **List**:
```go
items := cache.List() // Список всех элементов кэша
for _, item := range items {
fmt.Printf("Ключ: %s, Значение: %v, Момент устаревания: %d\n", item.Key, item.Item.Data(), item.Item.DestroyTimestamp())
}
```
Получить список устаревших элементов можно так
```go
items := ExpiredList()
```
### Получение количества элементов
Для получения количества элементов в кэше используйте метод **Count**:
```go
count := cache.Count() // Количество элементов в кэше
```
### Получение размера кэша
Для получения размера всего кэша в байтах используйте метод **Size**:
```go
size := cache.Size() // Размер кэша в байтах
```
Данный метод возвращает корректное значение, если в кэше элементы представлены этими типами данных:
```go
bool
int, int8, int16, int32, int64
uint, uint8, uint16, uint32, uint64, uintptr
float32, float64
complex64, complex128
array, slice, string
map, struct, func
```
**И композициями этих типов**.
В противном случае значение может быть не точным.
## Работа с дампами
В модуле присутствуют методы **Save** и **Load**, позволяющие создавать и загружать дампы. Эти методы удовлетворяют интерфейсам **io.Writer** и **io.Reader** соответственно, т.е. их можно использовать и для работы с файлами, и для работы с буфферами.
### Сценарий 1
```go
type Person struct {
Name string
Age int
Hobbies []string
}
cache := candycache.Cacher(10 * time.Minute) // Создаем кэш с интервалом очистки 10 минут
cache.Set("key1", "string", 5*time.Minute)
cache.Set("key2", 2, 10*time.Minute)
cache.Set("key7", -2.1231, 10*time.Minute)
cache.Set("key3", []string{"string1", "string2"}, 10*time.Minute)
cache.Set("key4", map[string]int{"a": 1, "b": 2}, 10*time.Minute)
cache.Set("key5", Person{Name: "Alice", Age: 30, Hobbies: []string{"reading", "swimming"}}, 10*time.Minute)
cache.Set("key6", []Person{
{Name: "Bob", Age: 25, Hobbies: []string{"coding", "gaming"}},
{Name: "Charlie", Age: 35, Hobbies: []string{"hiking", "photography"}},
}, 10*time.Minute)
file, err := os.Create("cache_dump.json")
if err != nil {
log.Fatal("error creating file: ", err)
}
if err := cache.Save(file); err != nil { // Сохранение кэша в файл
log.Fatal("error saving cache: ", err)
}
file.Close()
cache.Flush() // Удаление всех элементов из кэша
file, err = os.Open("cache_dump.json")
if err != nil {
log.Fatal("error opening file: ", err)
}
if err := cache.Load(file); err != nil { // Загрузка кэша из файла
fmt.Println("error load cache:", err)
}
list := cache.List() // Получаю список элементов кэша
for _, i := range list {
fmt.Println(i.Key, i.Item.Data(), i.Item.DestroyTimestamp())
}
```
### Сценарий 2
```go
type Person struct {
Name string
Age int
Hobbies []string
}
cache := candycache.Cacher(10 * time.Minute) // Создаем кэш с интервалом очистки 10 минут
cache.Set("key1", "string", 5*time.Minute)
cache.Set("key2", 2, 10*time.Minute)
cache.Set("key7", -2.1231, 10*time.Minute)
cache.Set("key3", []string{"string1", "string2"}, 10*time.Minute)
cache.Set("key4", map[string]int{"a": 1, "b": 2}, 10*time.Minute)
cache.Set("key5", Person{Name: "Alice", Age: 30, Hobbies: []string{"reading", "swimming"}}, 10*time.Minute)
cache.Set("key6", []Person{
{Name: "Bob", Age: 25, Hobbies: []string{"coding", "gaming"}},
{Name: "Charlie", Age: 35, Hobbies: []string{"hiking", "photography"}},
}, 10*time.Minute)
var buffer bytes.Buffer
if err := cache.Save(&buffer); err != nil { // Сохранение бэкапа
log.Fatal("error saving cache: ", err)
}
cache.Set("key1", "lost", -1)
cache.Set("key2", "lost", -1)
cache.Set("key3", "lost", -1)
cache.Set("key4", "lost", -1)
cache.Set("key5", "lost", -1)
cache.Set("key6", "lost", -1)
cache.Set("key7", "lost", -1)
if err := cache.Load(&buffer); err != nil { // Восстановление бэкапа
log.Fatal("error loading cache: ", err)
}
list := cache.List() // Получаю список элементов кэша
for _, i := range list {
fmt.Println(i.Key, i.Item.Data(), i.Item.DestroyTimestamp())
}
```

View File

@ -1,21 +1,12 @@
package candycache package simplecache
import ( import (
"encoding/json"
"errors" "errors"
"io"
"reflect" "reflect"
"sync" "sync"
"time" "time"
) )
// JSON структура для создания/загрузки дампов
type Dump struct {
Key string `json:"key"`
DestroyTimestamp int64 `json:"destroyTimestamp"`
Data interface{} `json:"data"`
}
// Структура виде ключ-значение для возвращения списка элементов кэша с их ключами. // Структура виде ключ-значение для возвращения списка элементов кэша с их ключами.
type KeyItemPair struct { type KeyItemPair struct {
Key string Key string
@ -36,14 +27,16 @@ type Cache struct {
cleanupInterval time.Duration // Интервал очистки хранилища в наносекундах cleanupInterval time.Duration // Интервал очистки хранилища в наносекундах
} }
// Создает новый экземпляр Cache с интервалом очистки cleanupInterval. // NewCache создает новый экземпляр Cache с интервалом очистки cleanupInterval.
// Если cleanupInterval < 0, то кэш не будет очищаться автоматически. // Если cleanupInterval < 0, то кэш не будет очищаться автоматически.
func Cacher(cleanupInterval time.Duration) *Cache { func NewCache(cleanupInterval time.Duration) *Cache {
cache := &Cache{ cache := &Cache{
storage: make(map[string]Item), storage: make(map[string]Item),
cleanupInterval: cleanupInterval, cleanupInterval: cleanupInterval,
} }
// Запускаем Garbage Collector если интервал очистки больше 0
// Иначе (если он отрицательный) кэш будет жить до ручного вызова Cleanup
if cleanupInterval > 0 { if cleanupInterval > 0 {
go cache.gc(cleanupInterval) go cache.gc(cleanupInterval)
} }
@ -67,7 +60,7 @@ func (c *Cache) Cleanup() {
defer c.Unlock() defer c.Unlock()
for key, item := range c.storage { for key, item := range c.storage {
if item.destroyTimestamp <= time.Now().UnixNano() { if item.destroyTimestamp <= time.Now().Unix() {
delete(c.storage, key) delete(c.storage, key)
} }
} }
@ -84,37 +77,18 @@ func (c *Cache) Flush() {
} }
// Получение элемента из кэша по ключу. // Получение элемента из кэша по ключу.
func (c *Cache) Get(key string) (interface{}, error) { func (c *Cache) Get(key string) (interface{}, bool) {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()
item, found := c.storage[key] item, found := c.storage[key]
// Элемент не найден в кэше
if !found { if !found {
return nil, errors.New("key not found") return nil, false
} }
return item.data, nil return item.data, true
}
// Определяет является ли элемент устаревшим.
// Вторым аргументов возвращается есть элемент в кэше или нет.
// Первым - устаревший элемент или нет.
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
}
} }
// Удаление элемента по ключу. // Удаление элемента по ключу.
@ -135,12 +109,12 @@ func (c *Cache) Delete(key string) error {
// key - ключ. // key - ключ.
// data - данные. // data - данные.
// ttl - время жизни элемента (time to life) в наносекундах. // ttl - время жизни элемента (time to life) в наносекундах.
func (c *Cache) Set(key string, data interface{}, ttl time.Duration) { func (c *Cache) Add(key string, data interface{}, ttl time.Duration) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
c.storage[key] = Item{ c.storage[key] = Item{
destroyTimestamp: time.Now().UnixNano() + int64(ttl), destroyTimestamp: time.Now().Unix() + int64(ttl.Seconds()),
data: data, data: data,
} }
} }
@ -153,13 +127,15 @@ func (c *Cache) Count() int {
return len(c.storage) return len(c.storage)
} }
// Возвращает список всех элементов кэша. // Печать всех элементов кэша (ключ и время уничтожения).
func (c *Cache) List() []KeyItemPair { func (c *Cache) List() []KeyItemPair {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()
items := []KeyItemPair{} // Создаем срез для хранения пар ключ-значение
items := make([]KeyItemPair, 0, len(c.storage))
// Заполняем срез парами ключ-значение
for key, item := range c.storage { for key, item := range c.storage {
items = append(items, KeyItemPair{Key: key, Item: item}) items = append(items, KeyItemPair{Key: key, Item: item})
} }
@ -167,22 +143,6 @@ func (c *Cache) List() []KeyItemPair {
return items 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 { func (c *Cache) Size() int {
c.RLock() c.RLock()
@ -196,74 +156,29 @@ func (c *Cache) Size() int {
return size return size
} }
// Save сохраняет кэш в io.Writer в формате JSON, записывая каждый элемент по отдельности. // ПОДДЕРЖИВАЕМЫЕ ТИПЫ:
func (c *Cache) Save(w io.Writer) error { // Bool +
c.RLock() // Int +
defer c.RUnlock() // Int8 +
// Int16 +
if _, err := w.Write([]byte("[\n")); err != nil { // Int32 +
return err // Int64 +
} // Uint +
// Uint8 +
encoder := json.NewEncoder(w) // Uint16 +
first := true // Uint32 +
for key, item := range c.storage { // Uint64 +
entry := Dump{ // Uintptr +
Key: key, // Float32 +
DestroyTimestamp: item.destroyTimestamp, // Float64 +
Data: item.data, // Complex64 +
} // Complex128 +
// Array +
if !first { // Func +
if _, err := w.Write([]byte(",\n")); err != nil { // Map +
return err // Slice +
} // String +
} // Struct
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 {
return err
}
for decoder.More() {
entry := Dump{}
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 { func isize(i interface{}) int {
if i == nil { if i == nil {
return 0 return 0
@ -302,12 +217,3 @@ func (i *Item) Data() interface{} {
func (i *Item) DestroyTimestamp() int64 { func (i *Item) DestroyTimestamp() int64 {
return i.destroyTimestamp return i.destroyTimestamp
} }
// Определяет является ли элемент устаревшим.
func (i *Item) IsExpired() bool {
if i.destroyTimestamp <= time.Now().UnixNano() {
return true
} else {
return false
}
}