Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Deal.Telegram.Caching;
|
||||
|
||||
/// <summary>
|
||||
/// Кэш с ограниченной ёмкостью и вытеснением least-recently-used (LRU): при переполнении удаляется
|
||||
/// элемент, к которому дольше всего не обращались. Нужен там, где раньше жил неограниченный
|
||||
/// <see cref="Dictionary{TKey,TValue}"/> и память росла с числом сущностей (долгоживущий процесс сервиса).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// НЕ потокобезопасен: вызывающий обязан сериализовать доступ (в telegram-service кэши
|
||||
/// WTelegramSessionClient защищены общим <c>_entityCacheGate</c>). Вытеснение — забота производительности,
|
||||
/// а не корректности: потерянное значение всегда можно получить повторным запросом к Telegram.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TKey">Тип ключа (ссылочный или значимый).</typeparam>
|
||||
/// <typeparam name="TValue">Тип значения.</typeparam>
|
||||
public sealed class LruCache<TKey, TValue>
|
||||
where TKey : notnull
|
||||
{
|
||||
private readonly Dictionary<TKey, LinkedListNode<Entry>> _map;
|
||||
private readonly LinkedList<Entry> _recency = new();
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт кэш заданной ёмкости.
|
||||
/// </summary>
|
||||
/// <param name="capacity">Максимальное число элементов (должно быть больше нуля).</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Ёмкость не положительна.</exception>
|
||||
public LruCache(int capacity)
|
||||
{
|
||||
if (capacity <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity), capacity, "Ёмкость LRU-кэша должна быть положительной");
|
||||
}
|
||||
|
||||
Capacity = capacity;
|
||||
_map = new Dictionary<TKey, LinkedListNode<Entry>>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Максимальное число элементов кэша.
|
||||
/// </summary>
|
||||
public int Capacity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Текущее число элементов кэша.
|
||||
/// </summary>
|
||||
public int Count => _map.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Пробует получить значение по ключу, отмечая его как недавно использованный.
|
||||
/// </summary>
|
||||
/// <param name="key">Ключ.</param>
|
||||
/// <param name="value">Найденное значение (иначе значение по умолчанию).</param>
|
||||
/// <returns>True — ключ найден.</returns>
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
||||
{
|
||||
if (!_map.TryGetValue(key, out LinkedListNode<Entry>? node))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
Touch(node);
|
||||
value = node.Value.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет наличие ключа, не меняя недавность (лёгкая проверка без вытеснения).
|
||||
/// </summary>
|
||||
/// <param name="key">Ключ.</param>
|
||||
/// <returns>True — ключ есть в кэше.</returns>
|
||||
public bool ContainsKey(TKey key) => _map.ContainsKey(key);
|
||||
|
||||
/// <summary>
|
||||
/// Записывает значение по ключу (вставка или обновление) и вытесняет самый давний элемент при
|
||||
/// переполнении. Обновление существующего ключа делает его самым недавним.
|
||||
/// </summary>
|
||||
/// <param name="key">Ключ.</param>
|
||||
/// <param name="value">Значение.</param>
|
||||
public void Set(TKey key, TValue value)
|
||||
{
|
||||
if (_map.TryGetValue(key, out LinkedListNode<Entry>? existing))
|
||||
{
|
||||
existing.Value = new Entry(key, value);
|
||||
Touch(existing);
|
||||
return;
|
||||
}
|
||||
|
||||
var node = new LinkedListNode<Entry>(new Entry(key, value));
|
||||
_recency.AddFirst(node);
|
||||
_map[key] = node;
|
||||
|
||||
if (_map.Count > Capacity)
|
||||
{
|
||||
LinkedListNode<Entry> oldest = _recency.Last!;
|
||||
_recency.RemoveLast();
|
||||
_map.Remove(oldest.Value.Key);
|
||||
}
|
||||
}
|
||||
|
||||
// Перемещает узел в начало списка недавности (последний использованный).
|
||||
// node: Узел кэша.
|
||||
private void Touch(LinkedListNode<Entry> node)
|
||||
{
|
||||
if (!ReferenceEquals(node, _recency.First))
|
||||
{
|
||||
_recency.Remove(node);
|
||||
_recency.AddFirst(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Элемент кэша (ключ хранится для удаления при вытеснении из списка недавности).
|
||||
// Key: Ключ.
|
||||
// Value: Значение.
|
||||
private readonly record struct Entry(TKey Key, TValue Value);
|
||||
}
|
||||
Reference in New Issue
Block a user