package com.baeldung.lrucache; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; public class LRUCache implements Cache { private int size; private Map>> linkedListNodeMap; private DoublyLinkedList> doublyLinkedList; private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public LRUCache(int size) { this.size = size; this.linkedListNodeMap = new ConcurrentHashMap<>(size); this.doublyLinkedList = new DoublyLinkedList<>(); } @Override public boolean put(K key, V value) { this.lock.writeLock().lock(); try { CacheElement item = new CacheElement(key, value); LinkedListNode> newNode; if (this.linkedListNodeMap.containsKey(key)) { LinkedListNode> node = this.linkedListNodeMap.get(key); newNode = doublyLinkedList.updateAndMoveToFront(node, item); } else { if (this.size() >= this.size) { this.evictElement(); } newNode = this.doublyLinkedList.add(item); } if (newNode.isEmpty()) { return false; } this.linkedListNodeMap.put(key, newNode); return true; } finally { this.lock.writeLock().unlock(); } } @Override public Optional get(K key) { this.lock.readLock().lock(); try { LinkedListNode> linkedListNode = this.linkedListNodeMap.get(key); if (linkedListNode != null && !linkedListNode.isEmpty()) { linkedListNodeMap.put(key, this.doublyLinkedList.moveToFront(linkedListNode)); return Optional.of(linkedListNode.getElement().getValue()); } return Optional.empty(); } finally { this.lock.readLock().unlock(); } } @Override public int size() { this.lock.readLock().lock(); try { return doublyLinkedList.size(); } finally { this.lock.readLock().unlock(); } } @Override public boolean isEmpty() { return size() == 0; } @Override public void clear() { this.lock.writeLock().lock(); try { linkedListNodeMap.clear(); doublyLinkedList.clear(); } finally { this.lock.writeLock().unlock(); } } private boolean evictElement() { this.lock.writeLock().lock(); try { LinkedListNode> linkedListNode = doublyLinkedList.removeTail(); if (linkedListNode.isEmpty()) { return false; } linkedListNodeMap.remove(linkedListNode.getElement().getKey()); return true; } finally { this.lock.writeLock().unlock(); } } }