Как реализовать простое кэширование в Java?

Ответ

Самый простой способ — использовать HashMap для хранения данных в памяти.

public class SimpleCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();

    public void put(K key, V value) {
        cache.put(key, value);
    }

    public V get(K key) {
        return cache.get(key);
    }

    public void remove(K key) {
        cache.remove(key);
    }
}

Почему это просто, но не всегда достаточно:

  • Плюсы: Минимальная реализация, быстрый доступ O(1).
  • Минусы:
    • Нет ограничения размера (риск OutOfMemoryError).
    • Нет политики вытеснения (например, LRU).
    • Нет TTL (Time-To-Live) для устаревания записей.
    • Не потокобезопасно для HashMap.

Практические улучшения:

  1. Для потокобезопасности используйте ConcurrentHashMap.
  2. Для production-систем применяйте специализированные библиотеки:
    • Caffeine: Высокопроизводительный, современный кэш.
    • Guava Cache: От Google, богатый API (размер, TTL, статистика).

Пример с ConcurrentHashMap и LRU-подобным поведением:

public class SimpleConcurrentCache<K, V> {
    private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
    private final int maxSize;
    private final LinkedBlockingQueue<K> order = new LinkedBlockingQueue<>();

    public SimpleConcurrentCache(int maxSize) { this.maxSize = maxSize; }

    public void put(K key, V value) {
        if (cache.size() >= maxSize) {
            K oldestKey = order.poll(); // Удаляем самый старый ключ
            if (oldestKey != null) cache.remove(oldestKey);
        }
        cache.put(key, value);
        order.offer(key);
    }
    // ... get, remove
}

Ответ 18+ 🔞

Ага, смотри, самый примитивный способ — это взять HashMap и просто туда всё пихать, как в помойное ведро.

public class SimpleCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();

    public void put(K key, V value) {
        cache.put(key, value);
    }

    public V get(K key) {
        return cache.get(key);
    }

    public void remove(K key) {
        cache.remove(key);
    }
}

Ну и что тут такого, спросишь ты? А то, что это как взять одноразовую тарелку на всю жизнь — вроде работает, пока не начнёт протекать, блядь.

  • Плюсы: Проще некуда, доступ быстрый — O(1), как удар током.
  • Минусы, ёпта, их овердохуища:
    • Размер не ограничен. Закидывай данные, пока не лопнет, как жадный удав на ферме мышей. OutOfMemoryError тебе в сраку и привет.
    • Никакой политики вытеснения. Кто зашёл первым, тот и сидит вечно, как бабка на лавочке. Никакого LRU, LFU — нихуя.
    • TTL? А что это? Записи не стареют, они тут бессмертные, как вампиры-алкаши.
    • Потокобезопасность? Да какая, на хуй, потокобезопасность у обычного HashMap? Это как зайти в общий сортир без дверей — всё закончится пиздецом.

Ну а если по-взрослому, то:

  1. Чтобы не было драк между потоками, бери ConcurrentHashMap. Это уже как сортир с кабинками.
  2. А для продакшена, где всё серьёзно, лучше вообще не выёбывайся и бери готовые библиотеки:
    • Caffeine: Быстрый, современный, прокачанный. Как спортивная тачка.
    • Guava Cache: От гугла, с кучей прибамбасов — и размер задать можно, и время жизни записи, и статистику посмотреть.

Вот, смотри, примерчик чутка поумнее, с ConcurrentHashMap и намёком на LRU (хоть и кривоватым):

public class SimpleConcurrentCache<K, V> {
    private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
    private final int maxSize;
    private final LinkedBlockingQueue<K> order = new LinkedBlockingQueue<>();

    public SimpleConcurrentCache(int maxSize) { this.maxSize = maxSize; }

    public void put(K key, V value) {
        if (cache.size() >= maxSize) {
            K oldestKey = order.poll(); // Выковыриваем того, кто дольше всех сидит
            if (oldestKey != null) cache.remove(oldestKey);
        }
        cache.put(key, value);
        order.offer(key);
    }
    // ... get, remove
}

Но это всё равно, блядь, велосипед с квадратными колёсами. Для учебки сойдёт, а так — бери готовое, не мудри.