Получить доступ

Как получить LRU-кэш на базе LinkedHashMap?

MiddleКоллекции
Ещё спрашивают так:
  • Нужен LRU-кэш на 1000 записей. Как сделать его на LinkedHashMap и за счёт чего это работает?
Коротко

new LinkedHashMap<>(cap, 0.75f, true) (accessOrder) + переопределённый removeEldestEntry, возвращающий size() > max. Get переносит запись в конец, самая давно неиспользуемая — в голове и вытесняется.

  • Без accessOrder получится FIFO, а не LRU
  • Не потокобезопасно — для многих потоков Caffeine
  • Всё за O(1)

А ещё Caffeine (W-TinyLFU) обычно даёт больше попаданий, чем LRU

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Попробуйте сами

LRU-кэш: хеш-таблица плюс двусвязный список

Подробно

Нужны обе части. Флаг accessOrder переводит внутренний список из порядка вставки в порядок обращения: каждый get перемещает запись в конец, поэтому в голове списка всегда самая давно не используемая. Метод removeEldestEntry вызывается после вставки и по умолчанию возвращает false — пока его не переопределить, никакого вытеснения не будет. Ограничения, которые стоит назвать: реализация не потокобезопасна, а вытеснение происходит только при добавлении новой записи, поэтому карта, в которую перестали писать, не сжимается. Для конкурентного кэша с TTL берут специализированную библиотеку.

Пример

class Lru<K, V> extends LinkedHashMap<K, V> {
    private final int max;
    Lru(int max) { super(16, 0.75f, true); this.max = max; }
    @Override protected boolean removeEldestEntry(Map.Entry<K, V> e) { return size() > max; }
}

Что хотят услышать

  • accessOrder и removeEldestEntry.
  • Почему работает и ограничения.

Частые ошибки

  • Задать accessOrder и забыть про removeEldestEntry.
  • Использовать такой кэш из нескольких потоков без синхронизации.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✓Создать с accessOrder = true и переопределить removeEldestEntry, возвращая true при превышении размера
  • ✗Достаточно создать LinkedHashMap обычным конструктором — вытеснение работает по умолчанию
  • ✗Переопределить equals и hashCode у ключа
  • ✗Обернуть HashMap в Collections.synchronizedMap

Что спросят дальше

  • Почему get меняет порядок при accessOrder = true?
  • Когда стоит взять готовую библиотеку кэширования вместо этого приёма?
  • Чем LRU отличается от LFU и когда какой выбирают?
  • Как оценить, что кэш выбранного размера вообще помогает?

Разбор темы

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем