← Назад к вопросам

В чём разница между кешем и словарём?

2.0 Middle🔥 192 комментариев
#Кэширование и Redis

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основные различия между кешем и словарём

Хотя кеш (cache) и словарь (dictionary) на первый взгляд кажутся похожими структурами данных (оба хранят пары "ключ-значение"), они имеют фундаментальные различия в назначении, поведении и реализации.

Ключевые концептуальные различия

АспектСловарьКеш
Основное назначениеХранение и быстрый доступ к даннымОптимизация производительности за счёт хранения "горячих" данных
Жизненный цикл данныхДанные хранятся, пока явно не удаленыДанные могут автоматически удаляться (по TTL, при нехватке памяти)
Семантика операцийДетерминированное поведениеНегарантированное поведение (данные могут "исчезнуть")
Критерии эффективностиСкорость операций, памятьКоэффициент попаданий (hit ratio), снижение латентности

Техническая реализация в C#

Словарь (Dictionary<TKey, TValue>)

// Простое хранилище данных с детерминированным поведением
Dictionary<string, User> userDictionary = new();

// Добавление данных - они останутся там, пока явно не удалены
userDictionary.Add("user123", new User { Id = "user123", Name = "Алексей" });

// Гарантированный доступ (если ключ существует)
if (userDictionary.TryGetValue("user123", out User user))
{
    Console.WriteLine($"Найден пользователь: {user.Name}");
}

// Данные не удаляются автоматически - требуется явное управление
userDictionary.Remove("user123");

Кеш (MemoryCache / IDistributedCache)

using Microsoft.Extensions.Caching.Memory;

// Создание кеша с политиками
IMemoryCache cache = new MemoryCache(new MemoryCacheOptions
{
    SizeLimit = 1024, // Ограничение по размеру
    CompactionPercentage = 0.2 // Процент очистки при нехватке памяти
});

// Добавление с политиками истечения срока
var cacheEntryOptions = new MemoryCacheEntryOptions()
    .SetSlidingExpiration(TimeSpan.FromMinutes(10)) // "Скользящее" истечение
    .SetAbsoluteExpiration(TimeSpan.FromHours(1))   // Абсолютное истечение
    .SetPriority(CacheItemPriority.High)            // Приоритет при вытеснении
    .SetSize(1);                                    // Размер записи для квот

cache.Set("user123", user, cacheEntryOptions);

// Негарантированный доступ - данные могли быть удалены
if (cache.TryGetValue("user123", out User cachedUser))
{
    // Попадание в кеш (cache hit)
    Console.WriteLine($"Данные из кеша: {cachedUser.Name}");
}
else
{
    // Промах кеша (cache miss) - нужно загрузить из источника
    var freshUser = await LoadUserFromDatabase("user123");
    cache.Set("user123", freshUser, cacheEntryOptions);
}

Стратегии вытеснения в кешах

Кеши используют различные алгоритмы вытеснения (eviction policies):

  1. LRU (Least Recently Used) - удаляет редко используемые данные
  2. LFU (Least Frequently Used) - удаляет наименее часто используемые
  3. FIFO (First In First Out) - удаляет по порядку добавления
  4. TTL (Time To Live) - удаляет по истечении времени
// Пример реализации простого LRU-кеша
public class LruCache<TKey, TValue> where TKey : notnull
{
    private readonly int _capacity;
    private readonly Dictionary<TKey, LinkedListNode<CacheItem>> _dictionary;
    private readonly LinkedList<CacheItem> _linkedList;

    public LruCache(int capacity)
    {
        _capacity = capacity;
        _dictionary = new Dictionary<TKey, LinkedListNode<CacheItem>>();
        _linkedList = new LinkedList<CacheItem>();
    }

    public TValue Get(TKey key)
    {
        if (_dictionary.TryGetValue(key, out var node))
        {
            // Перемещаем в начало как недавно использованный
            _linkedList.Remove(node);
            _linkedList.AddFirst(node);
            return node.Value.Value;
        }
        return default;
    }

    public void Put(TKey key, TValue value)
    {
        if (_dictionary.Count >= _capacity)
        {
            // Удаляем последний (наименее недавно использованный) элемент
            var lastNode = _linkedList.Last;
            _dictionary.Remove(lastNode.Value.Key);
            _linkedList.RemoveLast();
        }
        // Добавляем новый элемент в начало
        var newNode = new LinkedListNode<CacheItem>(new CacheItem(key, value));
        _linkedList.AddFirst(newNode);
        _dictionary[key] = newNode;
    }

    private class CacheItem
    {
        public TKey Key { get; }
        public TValue Value { get; }
        
        public CacheItem(TKey key, TValue value)
        {
            Key = key;
            Value = value;
        }
    }
}

Практические сценарии использования

Когда использовать словарь:

  • Хранение справочников и конфигураций
  • Кэширование в пределах одного запроса (Request-scoped caching)
  • Когда нужен полный контроль над жизненным циклом данных
  • Для группировки и агрегации данных (GroupBy эквивалент)

Когда использовать кеш:

  • Кеширование результатов дорогостоящих вычислений
  • Хранение сессий пользователей
  • Уменьшение нагрузки на базу данных
  • Распределённое хранение временных данных
  • Реализация rate limiting и других временных ограничений

Производительность и характеристики

Словарь обычно обеспечивает O(1) для операций поиска, но не управляет памятью автоматически. Кеш добавляет накладные расходы на управление политиками вытеснения, но предотвращает неконтролируемый рост использования памяти.

// Пример гибридного подхода: словарь с элементами управления памятью
public class ManagedDictionary<TKey, TValue> where TKey : notnull
{
    private readonly Dictionary<TKey, (TValue value, DateTime added)> _storage;
    private readonly int _maxItems;
    private readonly object _lock = new();

    public ManagedDictionary(int maxItems)
    {
        _storage = new Dictionary<TKey, (TValue, DateTime)>();
        _maxItems = maxItems;
    }

    public void Add(TKey key, TValue value)
    {
        lock (_lock)
        {
            // Автоматическая очистка старых записей
            if (_storage.Count >= _maxItems)
            {
                var oldest = _storage.OrderBy(x => x.Value.added).First();
                _storage.Remove(oldest.Key);
            }
            
            _storage[key] = (value, DateTime.UtcNow);
        }
    }
}

Распределённые кеши

В отличие от локальных словарей, распределённые кеши (Redis, Memcached) обеспечивают:

  • Согласованность данных между несколькими экземплярами приложения
  • Отказоустойчивость и персистентность
  • Разделение кеша между различными сервисами
// Использование распределённого кеша в ASP.NET Core
services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
    options.InstanceName = "SampleInstance";
});

// Внедрение и использование
public class UserService
{
    private readonly IDistributedCache _cache;
    
    public async Task<User> GetUserAsync(string userId)
    {
        var key = $"user:{userId}";
        var cachedData = await _cache.GetStringAsync(key);
        
        if (cachedData != null)
        {
            return JsonSerializer.Deserialize<User>(cachedData);
        }
        
        var user = await LoadFromDatabase(userId);
        await _cache.SetStringAsync(key, 
            JsonSerializer.Serialize(user),
            new DistributedCacheEntryOptions
            {
                SlidingExpiration = TimeSpan.FromMinutes(10)
            });
        
        return user;
    }
}

Заключение

Основное отличие заключается в семантике гарантий: словарь гарантирует хранение данных до явного удаления, в то время как кеш предоставляет условные гарантии с автоматическим управлением жизненным циклом. Словарь — это структура данных для детерминированного хранения, а кеш — архитектурный паттерн для оптимизации производительности с учетом ограничений ресурсов.

На практике часто используются вместе: например, кеш может быть реализован на основе словаря, но с добавлением политик вытеснения, или словарь может использоваться для быстрого доступа к часто изменяющимся данным в памяти, в то время как кеш хранит более статичные данные из внешних источников.

В чём разница между кешем и словарём? | PrepBro