Compare commits

...

59 Commits
v5.0.0 ... main

Author SHA1 Message Date
serr f66e67e4d7 readme.md 2025-02-02 21:57:59 +03:00
serr c70143ac5b readme md 2025-01-06 15:24:23 +03:00
serr 67278d8e46 readme md 2025-01-06 15:00:54 +03:00
serr bfbf496836 readme md 2025-01-06 15:00:05 +03:00
serr 666e8ef986 readme md 2025-01-06 14:59:46 +03:00
serr 8026821dad readme md 2025-01-06 14:58:26 +03:00
serr 7c74d58155 readme md 2025-01-06 14:57:32 +03:00
serr 6ff57692ce readme md 2025-01-06 14:54:10 +03:00
serr 7a2a846273 readme md 2025-01-06 14:52:30 +03:00
serr 0bb6297df3 readme md 2025-01-06 14:51:57 +03:00
serr cae8611d7b readme md 2025-01-06 14:48:47 +03:00
serr aa69c168f8 readme md 2025-01-06 14:46:09 +03:00
serr ffce25b166 readme md 2025-01-06 14:45:35 +03:00
serr 69769f8c22 readme md 2025-01-06 14:44:22 +03:00
serr f70e396bca readme md 2025-01-06 14:39:05 +03:00
serr f4d9a23e79 readme md 2025-01-06 14:35:53 +03:00
serr afa9d82c59 Добавил возможность делать дампы кэша и восстанавливать их 2025-01-06 14:33:11 +03:00
serr 469c273292 Добавил возможность делать дампы кэша и восстанавливать их 2025-01-06 14:32:37 +03:00
serr d369267c21 Добавил возможность делать дампы кэша и восстанавливать их 2025-01-06 14:25:52 +03:00
serr 9ddbc94e9c readme md 2025-01-06 13:13:18 +03:00
serr 43985017e8 Добавил возврат ошибок 2025-01-06 13:09:55 +03:00
serr 34afa23b01 Добавлена возможность получить список устаревших элементов 2025-01-06 12:59:19 +03:00
serr 237da6e002 Добавлена возможность получить список устаревших элементов 2025-01-06 12:58:29 +03:00
serr 7ce16788c6 add -> set 2025-01-06 12:27:28 +03:00
serr c5863cb2b6 readme md 2025-01-06 12:20:31 +03:00
serr 30e8a0aa17 readme md 2025-01-06 12:19:06 +03:00
serr 653c140664 readme md 2025-01-06 12:18:20 +03:00
serr 6548230029 readme md 2025-01-06 12:17:29 +03:00
serr 60fbb406c0 readme md 2025-01-06 02:17:35 +03:00
serr 1b64c2955e readme md 2025-01-06 01:57:18 +03:00
serr b1c6e058da readme md 2025-01-06 01:56:17 +03:00
serr 083d1147a5 readme md 2025-01-06 01:56:04 +03:00
serr c6d0823ff6 readme md 2025-01-06 01:54:18 +03:00
serr e4c2c92d95 readme md 2025-01-06 01:50:45 +03:00
serr de6b44bcd2 readme md 2025-01-06 01:41:01 +03:00
serr a54debddcc readme md 2025-01-06 01:40:45 +03:00
serr 78183ec9b2 readme md 2025-01-06 01:36:39 +03:00
serr f3d84635e9 readme md 2025-01-06 01:30:42 +03:00
serr 7e009d870c readme md 2025-01-06 01:29:44 +03:00
serr 75c0f387bb readme md 2025-01-06 01:26:40 +03:00
serr fca8b3d9ff readme md 2025-01-06 01:24:59 +03:00
serr 09941f7723 readme md 2025-01-06 01:24:25 +03:00
serr eae6ab252d readme md 2025-01-06 01:21:42 +03:00
serr 485806dc57 readme md 2025-01-06 01:21:27 +03:00
serr 615a682595 readme md 2025-01-06 01:19:02 +03:00
serr 71aa7454f6 readme md 2025-01-06 01:18:35 +03:00
serr 86a95fe61d readme md 2025-01-06 01:17:53 +03:00
serr 5a4db2dfd4 readme md 2025-01-06 01:15:58 +03:00
serr 995f93f94b readme md 2025-01-06 01:14:07 +03:00
serr 6a0fe5a584 readme md 2025-01-06 01:13:25 +03:00
serr f79243bc42 readme md 2025-01-06 01:05:48 +03:00
serr da8b0916a1 readme md 2025-01-06 01:04:52 +03:00
serr b26c217492 readme md 2025-01-06 01:03:26 +03:00
serr 73c8a8860e readme md 2025-01-06 01:02:55 +03:00
serr 88d3780d57 readme md 2025-01-06 01:01:54 +03:00
serr 950f254211 readme md 2025-01-06 01:01:06 +03:00
serr 19f7143373 readme md 2025-01-06 01:00:23 +03:00
serr 692b011eda readme md 2025-01-06 00:44:03 +03:00
serr 54f2505afb readme.md test 2025-01-06 00:39:18 +03:00
2 changed files with 365 additions and 36 deletions

235
README.md Normal file
View File

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