SimpleLruCache.java
- /*
- * Copyright (C) 2019, Marc Strapetz <marc.strapetz@syntevo.com>
- * Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- package org.eclipse.jgit.util;
- import java.text.MessageFormat;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.List;
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- import org.eclipse.jgit.annotations.NonNull;
- import org.eclipse.jgit.internal.JGitText;
- /**
- * Simple limited size cache based on ConcurrentHashMap purging entries in LRU
- * order when reaching size limit
- *
- * @param <K>
- * the type of keys maintained by this cache
- * @param <V>
- * the type of mapped values
- *
- * @since 5.1.9
- */
- public class SimpleLruCache<K, V> {
- private static class Entry<K, V> {
- private final K key;
- private final V value;
- // pseudo clock timestamp of the last access to this entry
- private volatile long lastAccessed;
- private long lastAccessedSorting;
- Entry(K key, V value, long lastAccessed) {
- this.key = key;
- this.value = value;
- this.lastAccessed = lastAccessed;
- }
- void copyAccessTime() {
- lastAccessedSorting = lastAccessed;
- }
- @SuppressWarnings("nls")
- @Override
- public String toString() {
- return "Entry [lastAccessed=" + lastAccessed + ", key=" + key
- + ", value=" + value + "]";
- }
- }
- private Lock lock = new ReentrantLock();
- private Map<K, Entry<K,V>> map = new ConcurrentHashMap<>();
- private volatile int maximumSize;
- private int purgeSize;
- // pseudo clock to implement LRU order of access to entries
- private volatile long time = 0L;
- private static void checkPurgeFactor(float purgeFactor) {
- if (purgeFactor <= 0 || purgeFactor >= 1) {
- throw new IllegalArgumentException(
- MessageFormat.format(JGitText.get().invalidPurgeFactor,
- Float.valueOf(purgeFactor)));
- }
- }
- private static int purgeSize(int maxSize, float purgeFactor) {
- return (int) ((1 - purgeFactor) * maxSize);
- }
- /**
- * Create a new cache
- *
- * @param maxSize
- * maximum size of the cache, to reduce need for synchronization
- * this is not a hard limit. The real size of the cache could be
- * slightly above this maximum if multiple threads put new values
- * concurrently
- * @param purgeFactor
- * when the size of the map reaches maxSize the oldest entries
- * will be purged to free up some space for new entries,
- * {@code purgeFactor} is the fraction of {@code maxSize} to
- * purge when this happens
- */
- public SimpleLruCache(int maxSize, float purgeFactor) {
- checkPurgeFactor(purgeFactor);
- this.maximumSize = maxSize;
- this.purgeSize = purgeSize(maxSize, purgeFactor);
- }
- /**
- * Returns the value to which the specified key is mapped, or {@code null}
- * if this map contains no mapping for the key.
- *
- * <p>
- * More formally, if this cache contains a mapping from a key {@code k} to a
- * value {@code v} such that {@code key.equals(k)}, then this method returns
- * {@code v}; otherwise it returns {@code null}. (There can be at most one
- * such mapping.)
- *
- * @param key
- * the key
- *
- * @throws NullPointerException
- * if the specified key is null
- *
- * @return value mapped for this key, or {@code null} if no value is mapped
- */
- @SuppressWarnings("NonAtomicVolatileUpdate")
- public V get(Object key) {
- Entry<K, V> entry = map.get(key);
- if (entry != null) {
- entry.lastAccessed = tick();
- return entry.value;
- }
- return null;
- }
- /**
- * Maps the specified key to the specified value in this cache. Neither the
- * key nor the value can be null.
- *
- * <p>
- * The value can be retrieved by calling the {@code get} method with a key
- * that is equal to the original key.
- *
- * @param key
- * key with which the specified value is to be associated
- * @param value
- * value to be associated with the specified key
- * @return the previous value associated with {@code key}, or {@code null}
- * if there was no mapping for {@code key}
- * @throws NullPointerException
- * if the specified key or value is null
- */
- @SuppressWarnings("NonAtomicVolatileUpdate")
- public V put(@NonNull K key, @NonNull V value) {
- map.put(key, new Entry<>(key, value, tick()));
- if (map.size() > maximumSize) {
- purge();
- }
- return value;
- }
- @SuppressWarnings("NonAtomicVolatileUpdate")
- private long tick() {
- return ++time;
- }
- /**
- * Returns the current size of this cache
- *
- * @return the number of key-value mappings in this cache
- */
- public int size() {
- return map.size();
- }
- /**
- * Reconfigures the cache. If {@code maxSize} is reduced some entries will
- * be purged.
- *
- * @param maxSize
- * maximum size of the cache
- *
- * @param purgeFactor
- * when the size of the map reaches maxSize the oldest entries
- * will be purged to free up some space for new entries,
- * {@code purgeFactor} is the fraction of {@code maxSize} to
- * purge when this happens
- */
- public void configure(int maxSize, float purgeFactor) {
- lock.lock();
- try {
- checkPurgeFactor(purgeFactor);
- this.maximumSize = maxSize;
- this.purgeSize = purgeSize(maxSize, purgeFactor);
- if (map.size() >= maximumSize) {
- purge();
- }
- } finally {
- lock.unlock();
- }
- }
- private void purge() {
- // don't try to compete if another thread already has the lock
- if (lock.tryLock()) {
- try {
- List<Entry> entriesToPurge = new ArrayList<>(map.values());
- // copy access times to avoid other threads interfere with
- // sorting
- for (Entry e : entriesToPurge) {
- e.copyAccessTime();
- }
- Collections.sort(entriesToPurge,
- Comparator.comparingLong(o -> -o.lastAccessedSorting));
- for (int index = purgeSize; index < entriesToPurge
- .size(); index++) {
- map.remove(entriesToPurge.get(index).key);
- }
- } finally {
- lock.unlock();
- }
- }
- }
- }