|
|
@@ -1,424 +1,422 @@
|
|
|
-/*
|
|
|
- * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
- * @copyright Ranides Atterwim
|
|
|
- * @license WTFPL
|
|
|
- * @url http://ranides.net/projects/assira
|
|
|
- */
|
|
|
-package net.ranides.assira.collection.maps;
|
|
|
-
|
|
|
-import java.lang.ref.Reference;
|
|
|
-import java.lang.ref.ReferenceQueue;
|
|
|
-import java.lang.ref.SoftReference;
|
|
|
-import java.time.Duration;
|
|
|
-import java.time.Instant;
|
|
|
-import java.util.AbstractMap;
|
|
|
-import java.util.ArrayDeque;
|
|
|
-import java.util.Collection;
|
|
|
-import java.util.Iterator;
|
|
|
-import java.util.LinkedHashMap;
|
|
|
-import java.util.Map;
|
|
|
-import java.util.Set;
|
|
|
-import java.util.function.Supplier;
|
|
|
-
|
|
|
-/**
|
|
|
- * <p>
|
|
|
- * Kolekcja zaprojektowana jako podręczna pamięć na obiekty, których koszt
|
|
|
- * utworzenia jest relatywnie wysoki. Kolekcja przechowuje {@code holdsize}
|
|
|
- * ostatnio używanych elementów, bez względu na to, czy istnieją inne referencje
|
|
|
- * do nich. Przez "użycie" rozumiemy dowolną operację zapis lub odczytu.
|
|
|
- * W szczególności taka strategia pozwala polegać na metodzie "containsKey",
|
|
|
- * która uchroni wartość przed natychmiastowym usunięciem - jeśli tylko stwierdzono,
|
|
|
- * że klucz istnieje. Dodatkowo kolekcja przechowuje {@code cachesize} elementów
|
|
|
- * za pomocą WeakReference - obiekty te mogą zostać zwolnione przez GC, jeśli
|
|
|
- * zajdzie taka potrzeba.
|
|
|
- * </p><p>
|
|
|
- * Uwaga: wszystkie widoki zwracane przez "cache" są niebezpieczne w tym sensie,
|
|
|
- * że nie można wywoływać <b>żadnej</b> metody na obiekcie "cache" w czasie
|
|
|
- * iterowania po widoku. Jest to spowodowane tym, że cache uwzględnia czas dostępu
|
|
|
- * do obiektów: nawet odczyt zmienia wewnętrzną reprezentację. W efekcie, jeśli
|
|
|
- * podczas iteracji zostanie wywołana jakakolwiek inna metoda, to zostanie rzucone
|
|
|
- * ConcurentModificationException.
|
|
|
- * </p><p>
|
|
|
- * Known issue: holdlist contains duplicate entries. If you invoke #get(key) x times,
|
|
|
- * then "key" will be inserted to holdlist x times too. It's important observation, because
|
|
|
- * it means that other old keys will be discarded. In effect, we will protect last
|
|
|
- * {@code holdsize} "just keys" but not "distinct keys".
|
|
|
- * </p>
|
|
|
- *
|
|
|
- * @param <K>
|
|
|
- * @param <V>
|
|
|
- * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
- */
|
|
|
-@SuppressWarnings("element-type-mismatch")
|
|
|
-public class Cache<K,V> extends AbstractMap<K,V> {
|
|
|
-
|
|
|
- // @todo (assira #4) CacheTTL
|
|
|
-
|
|
|
- private final CacheMap<K,V> map;
|
|
|
-
|
|
|
- private final CacheList<V> list;
|
|
|
-
|
|
|
- private final Map<K,V> view;
|
|
|
-
|
|
|
- /**
|
|
|
- * Tworzy cache, który przechowuje maksymalnie 1024 elementy, w tym 32
|
|
|
- * ostatnio użyte są chronione przez usunięciem przez GC.
|
|
|
- */
|
|
|
- public Cache() {
|
|
|
- this(1024, 32);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Tworzy cache, którzy przechowuje {@code cachesize} ostatnio używanych
|
|
|
- * elementów, bez względu na to, czy istnieją do nich inne referencje.
|
|
|
- * @param cachesize ilość przechowywanych elementów
|
|
|
- */
|
|
|
- public Cache(int cachesize) {
|
|
|
- this(cachesize, cachesize);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Tworzy cache, którzy przechowuje {@code cachesize} ostatnio używanych
|
|
|
- * elementów, w tym chroni przez usunięciem przez GC ostatnie {@code holdsize}
|
|
|
- * elementów.
|
|
|
- * @param cachesize ilość przechowywanych elementów
|
|
|
- * @param holdsize ilość elementów chronionych przed GC
|
|
|
- */
|
|
|
- public Cache(int cachesize, int holdsize) {
|
|
|
- if( cachesize < holdsize ) {
|
|
|
- throw new IllegalArgumentException("cachesize must be greater or equal to holdsize");
|
|
|
- }
|
|
|
- this.list = new CacheList<>(Math.max(1,holdsize));
|
|
|
- this.map = new CRMap<>(cachesize);
|
|
|
- this.view = this.map.view();
|
|
|
- }
|
|
|
-
|
|
|
- public Cache(int cachesize, Duration ttl) {
|
|
|
- this(cachesize, cachesize, ttl);
|
|
|
- }
|
|
|
-
|
|
|
- public Cache(int cachesize, int holdsize, Duration ttl) {
|
|
|
- if( cachesize < holdsize ) {
|
|
|
- throw new IllegalArgumentException("cachesize must be greater or equal to holdsize");
|
|
|
- }
|
|
|
- this.list = new CacheList<>(Math.max(1,holdsize));
|
|
|
- this.map = new TRMap<>(cachesize, ttl);
|
|
|
- this.view = this.map.view();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean containsKey(Object key) {
|
|
|
- synchronized(this) {
|
|
|
- return null != get(key);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public V get(Object key) {
|
|
|
- synchronized(this) {
|
|
|
- V result = map.getValue(key);
|
|
|
- if (null != result) {
|
|
|
- list.hold(result);
|
|
|
- }
|
|
|
- return result;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public V get(K key, Supplier<V> supplier) {
|
|
|
- synchronized(this) {
|
|
|
- V result = map.getValue(key);
|
|
|
- if (null == result) {
|
|
|
- map.putValue(key, result = supplier.get());
|
|
|
- }
|
|
|
- list.hold(result);
|
|
|
- return result;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Wstawia wartość do cache'u i zwraca ją jako wynik. Jeśli w mapie
|
|
|
- * istniała już jakaś wartość, to ją zastępuje.
|
|
|
- * @param key
|
|
|
- * @param value
|
|
|
- * @return
|
|
|
- */
|
|
|
- public V pass(K key, V value) {
|
|
|
- synchronized(this) {
|
|
|
- map.release();
|
|
|
- map.putValue(key, value);
|
|
|
- return value;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public V put(K key, V value) {
|
|
|
- synchronized(this) {
|
|
|
- map.release();
|
|
|
- V prev = map.getValue(key);
|
|
|
- map.putValue(key, value);
|
|
|
- return prev;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public V remove(Object key) {
|
|
|
- synchronized(this) {
|
|
|
- map.release();
|
|
|
- return map.removeValue(key);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void clear() {
|
|
|
- synchronized(this) {
|
|
|
- list.clear();
|
|
|
- map.clear();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public int size() {
|
|
|
- synchronized(this) {
|
|
|
- map.release();
|
|
|
- return map.size();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Returns number of holded entries
|
|
|
- * Please note, that holdlist could contain duplicated entries,
|
|
|
- * so returned number could be deceptively greater than size of map.
|
|
|
- * @return
|
|
|
- */
|
|
|
- public int holdsize() {
|
|
|
- synchronized(this) {
|
|
|
- map.release();
|
|
|
- return list.size();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Holds value specified by key
|
|
|
- * @param key
|
|
|
- * @return
|
|
|
- */
|
|
|
- public boolean hold(K key) {
|
|
|
- synchronized(this) {
|
|
|
- return null != get(key);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Holds as many entries as possible
|
|
|
- * @return number of holded values
|
|
|
- */
|
|
|
- public int hold() {
|
|
|
- synchronized(this) {
|
|
|
- for(V value : values() ) {
|
|
|
- if(null != value) {
|
|
|
- list.hold(value);
|
|
|
- }
|
|
|
- }
|
|
|
- return list.size();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Set<Entry<K,V>> entrySet() {
|
|
|
- map.release();
|
|
|
- return view.entrySet();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Set<K> keySet() {
|
|
|
- map.release();
|
|
|
- return view.keySet();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Collection<V> values() {
|
|
|
- map.release();
|
|
|
- return view.values();
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- private static <TK> TK asKey(Reference<?> ref) {
|
|
|
- return (ref != null) ? ((KRef<TK,?>)ref).key : null;
|
|
|
- }
|
|
|
-
|
|
|
- private static class KRef<K,V> extends SoftReference<V> {
|
|
|
-
|
|
|
- public final K key;
|
|
|
-
|
|
|
- public KRef(K key, V value, ReferenceQueue<V> queue) {
|
|
|
- super(value, queue);
|
|
|
- this.key = key;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static class TRef<K,V> extends KRef<K,V> {
|
|
|
-
|
|
|
- public Instant time;
|
|
|
-
|
|
|
- public TRef(K key, V value, ReferenceQueue<V> queue, Duration ttl) {
|
|
|
- super(key, value, queue);
|
|
|
- this.time = Instant.now().plus(ttl);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public V get() {
|
|
|
- return time.isAfter(Instant.now()) ? null : super.get();
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private static class TRMap<K,V> extends LinkedHashMap<K, TRef<K,V>> implements CacheMap<K,V> {
|
|
|
-
|
|
|
- protected final int maxsize;
|
|
|
-
|
|
|
- protected final Duration ttl;
|
|
|
-
|
|
|
- protected transient final ReferenceQueue<V> queue = new ReferenceQueue<>();
|
|
|
-
|
|
|
- public TRMap(int maxsize, Duration ttl) {
|
|
|
- super( Math.min(Math.round(maxsize / 0.75f)+2, 2048), 0.75f, false);
|
|
|
- this.maxsize = maxsize;
|
|
|
- this.ttl = ttl;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected boolean removeEldestEntry(Entry<K, TRef<K,V>> eldest) {
|
|
|
- return this.size() > maxsize || eldest.getValue().time.isAfter(Instant.now());
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void release() {
|
|
|
- synchronized(this) {
|
|
|
- Reference<? extends V> ref;
|
|
|
- while ( null != (ref = queue.poll()) ) { remove( asKey(ref) ); }
|
|
|
-
|
|
|
- Instant now = Instant.now();
|
|
|
- Iterator<Entry<K, TRef<K, V>>> iterator = entrySet().iterator();
|
|
|
- while(iterator.hasNext()) {
|
|
|
- if(iterator.next().getValue().time.isBefore(now)) {
|
|
|
- break;
|
|
|
- }
|
|
|
- iterator.remove();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void putValue(K key, V value) {
|
|
|
- put(key, new TRef<>(key, value, queue, ttl));
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public V removeValue(Object key) {
|
|
|
- Reference<V> ref = remove(key);
|
|
|
- return (ref != null) ? ref.get() : null;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Map<K, V> view() {
|
|
|
- return MapUtils.map(this, v -> v.get());
|
|
|
- }
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- private static class CRMap<K,V> extends LinkedHashMap<K, KRef<K,V>> implements CacheMap<K,V> {
|
|
|
-
|
|
|
- protected final int maxsize;
|
|
|
-
|
|
|
- protected transient final ReferenceQueue<V> queue = new ReferenceQueue<>();
|
|
|
-
|
|
|
- public CRMap(int maxsize) {
|
|
|
- super( Math.min(Math.round(maxsize / 0.75f)+2, 2048), 0.75f, true);
|
|
|
- this.maxsize = maxsize;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected boolean removeEldestEntry(Entry<K, KRef<K,V>> eldest) {
|
|
|
- return this.size() > maxsize;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void release() {
|
|
|
- synchronized(this) {
|
|
|
- Reference<? extends V> ref;
|
|
|
- while ( null != (ref = queue.poll()) ) { remove( asKey(ref) ); }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void putValue(K key, V value) {
|
|
|
- put(key, new KRef<>(key, value, queue));
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public V removeValue(Object key) {
|
|
|
- Reference<V> ref = remove(key);
|
|
|
- return (ref != null) ? ref.get() : null;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Map<K, V> view() {
|
|
|
- return MapUtils.map(this, v -> v.get());
|
|
|
- }
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- private interface CacheMap<K,V> {
|
|
|
-
|
|
|
- Map<K,V> view();
|
|
|
-
|
|
|
- void release();
|
|
|
-
|
|
|
- void putValue(K key, V value);
|
|
|
-
|
|
|
- default V getValue(Object key) {
|
|
|
- Reference<V> ref = get(key);
|
|
|
- if(null == ref) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- V var = ref.get();
|
|
|
- if(null == var) {
|
|
|
- remove(key);
|
|
|
- }
|
|
|
- return var;
|
|
|
- }
|
|
|
-
|
|
|
- default V removeValue(Object key) {
|
|
|
- Reference<V> ref = remove(key);
|
|
|
- return (ref != null) ? ref.get() : null;
|
|
|
- }
|
|
|
-
|
|
|
- void clear();
|
|
|
-
|
|
|
- int size();
|
|
|
-
|
|
|
- Reference<V> get(Object key);
|
|
|
-
|
|
|
- Reference<V> remove(Object key);
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private static class CacheList<TV> extends ArrayDeque<TV> {
|
|
|
-
|
|
|
- private final int maxsize;
|
|
|
-
|
|
|
- public CacheList(int maxsize) {
|
|
|
- this.maxsize = maxsize;
|
|
|
- }
|
|
|
-
|
|
|
- public void hold(TV value) {
|
|
|
- addFirst(value);
|
|
|
- if (size() > maxsize) { removeLast(); }
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-}
|
|
|
+/*
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
+ * @license WTFPL
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
+ */
|
|
|
+package net.ranides.assira.collection.maps;
|
|
|
+
|
|
|
+import java.lang.ref.Reference;
|
|
|
+import java.lang.ref.ReferenceQueue;
|
|
|
+import java.lang.ref.SoftReference;
|
|
|
+import java.time.Duration;
|
|
|
+import java.time.Instant;
|
|
|
+import java.util.AbstractMap;
|
|
|
+import java.util.ArrayDeque;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.function.Supplier;
|
|
|
+
|
|
|
+/**
|
|
|
+ * <p>
|
|
|
+ * Kolekcja zaprojektowana jako podręczna pamięć na obiekty, których koszt
|
|
|
+ * utworzenia jest relatywnie wysoki. Kolekcja przechowuje {@code holdsize}
|
|
|
+ * ostatnio używanych elementów, bez względu na to, czy istnieją inne referencje
|
|
|
+ * do nich. Przez "użycie" rozumiemy dowolną operację zapis lub odczytu.
|
|
|
+ * W szczególności taka strategia pozwala polegać na metodzie "containsKey",
|
|
|
+ * która uchroni wartość przed natychmiastowym usunięciem - jeśli tylko stwierdzono,
|
|
|
+ * że klucz istnieje. Dodatkowo kolekcja przechowuje {@code cachesize} elementów
|
|
|
+ * za pomocą WeakReference - obiekty te mogą zostać zwolnione przez GC, jeśli
|
|
|
+ * zajdzie taka potrzeba.
|
|
|
+ * </p><p>
|
|
|
+ * Uwaga: wszystkie widoki zwracane przez "cache" są niebezpieczne w tym sensie,
|
|
|
+ * że nie można wywoływać <b>żadnej</b> metody na obiekcie "cache" w czasie
|
|
|
+ * iterowania po widoku. Jest to spowodowane tym, że cache uwzględnia czas dostępu
|
|
|
+ * do obiektów: nawet odczyt zmienia wewnętrzną reprezentację. W efekcie, jeśli
|
|
|
+ * podczas iteracji zostanie wywołana jakakolwiek inna metoda, to zostanie rzucone
|
|
|
+ * ConcurentModificationException.
|
|
|
+ * </p><p>
|
|
|
+ * Known issue: holdlist contains duplicate entries. If you invoke #get(key) x times,
|
|
|
+ * then "key" will be inserted to holdlist x times too. It's important observation, because
|
|
|
+ * it means that other old keys will be discarded. In effect, we will protect last
|
|
|
+ * {@code holdsize} "just keys" but not "distinct keys".
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param <K>
|
|
|
+ * @param <V>
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ */
|
|
|
+@SuppressWarnings("element-type-mismatch")
|
|
|
+public class Cache<K,V> extends AbstractMap<K,V> {
|
|
|
+
|
|
|
+ private final CacheMap<K,V> map;
|
|
|
+
|
|
|
+ private final CacheList<V> list;
|
|
|
+
|
|
|
+ private final Map<K,V> view;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Tworzy cache, który przechowuje maksymalnie 1024 elementy, w tym 32
|
|
|
+ * ostatnio użyte są chronione przez usunięciem przez GC.
|
|
|
+ */
|
|
|
+ public Cache() {
|
|
|
+ this(1024, 32);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Tworzy cache, którzy przechowuje {@code cachesize} ostatnio używanych
|
|
|
+ * elementów, bez względu na to, czy istnieją do nich inne referencje.
|
|
|
+ * @param cachesize ilość przechowywanych elementów
|
|
|
+ */
|
|
|
+ public Cache(int cachesize) {
|
|
|
+ this(cachesize, cachesize);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Tworzy cache, którzy przechowuje {@code cachesize} ostatnio używanych
|
|
|
+ * elementów, w tym chroni przez usunięciem przez GC ostatnie {@code holdsize}
|
|
|
+ * elementów.
|
|
|
+ * @param cachesize ilość przechowywanych elementów
|
|
|
+ * @param holdsize ilość elementów chronionych przed GC
|
|
|
+ */
|
|
|
+ public Cache(int cachesize, int holdsize) {
|
|
|
+ if( cachesize < holdsize ) {
|
|
|
+ throw new IllegalArgumentException("cachesize must be greater or equal to holdsize");
|
|
|
+ }
|
|
|
+ this.list = new CacheList<>(Math.max(1,holdsize));
|
|
|
+ this.map = new CRMap<>(cachesize);
|
|
|
+ this.view = this.map.view();
|
|
|
+ }
|
|
|
+
|
|
|
+ public Cache(int cachesize, Duration ttl) {
|
|
|
+ this(cachesize, cachesize, ttl);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Cache(int cachesize, int holdsize, Duration ttl) {
|
|
|
+ if( cachesize < holdsize ) {
|
|
|
+ throw new IllegalArgumentException("cachesize must be greater or equal to holdsize");
|
|
|
+ }
|
|
|
+ this.list = new CacheList<>(Math.max(1,holdsize));
|
|
|
+ this.map = new TRMap<>(cachesize, ttl);
|
|
|
+ this.view = this.map.view();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean containsKey(Object key) {
|
|
|
+ synchronized(this) {
|
|
|
+ return null != get(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public V get(Object key) {
|
|
|
+ synchronized(this) {
|
|
|
+ V result = map.getValue(key);
|
|
|
+ if (null != result) {
|
|
|
+ list.hold(result);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public V get(K key, Supplier<V> supplier) {
|
|
|
+ synchronized(this) {
|
|
|
+ V result = map.getValue(key);
|
|
|
+ if (null == result) {
|
|
|
+ map.putValue(key, result = supplier.get());
|
|
|
+ }
|
|
|
+ list.hold(result);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Wstawia wartość do cache'u i zwraca ją jako wynik. Jeśli w mapie
|
|
|
+ * istniała już jakaś wartość, to ją zastępuje.
|
|
|
+ * @param key
|
|
|
+ * @param value
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public V pass(K key, V value) {
|
|
|
+ synchronized(this) {
|
|
|
+ map.release();
|
|
|
+ map.putValue(key, value);
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public V put(K key, V value) {
|
|
|
+ synchronized(this) {
|
|
|
+ map.release();
|
|
|
+ V prev = map.getValue(key);
|
|
|
+ map.putValue(key, value);
|
|
|
+ return prev;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public V remove(Object key) {
|
|
|
+ synchronized(this) {
|
|
|
+ map.release();
|
|
|
+ return map.removeValue(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void clear() {
|
|
|
+ synchronized(this) {
|
|
|
+ list.clear();
|
|
|
+ map.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int size() {
|
|
|
+ synchronized(this) {
|
|
|
+ map.release();
|
|
|
+ return map.size();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns number of holded entries
|
|
|
+ * Please note, that holdlist could contain duplicated entries,
|
|
|
+ * so returned number could be deceptively greater than size of map.
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public int holdsize() {
|
|
|
+ synchronized(this) {
|
|
|
+ map.release();
|
|
|
+ return list.size();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Holds value specified by key
|
|
|
+ * @param key
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public boolean hold(K key) {
|
|
|
+ synchronized(this) {
|
|
|
+ return null != get(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Holds as many entries as possible
|
|
|
+ * @return number of holded values
|
|
|
+ */
|
|
|
+ public int hold() {
|
|
|
+ synchronized(this) {
|
|
|
+ for(V value : values() ) {
|
|
|
+ if(null != value) {
|
|
|
+ list.hold(value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return list.size();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<Entry<K,V>> entrySet() {
|
|
|
+ map.release();
|
|
|
+ return view.entrySet();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<K> keySet() {
|
|
|
+ map.release();
|
|
|
+ return view.keySet();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Collection<V> values() {
|
|
|
+ map.release();
|
|
|
+ return view.values();
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private static <TK> TK asKey(Reference<?> ref) {
|
|
|
+ return (ref != null) ? ((KRef<TK,?>)ref).key : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class KRef<K,V> extends SoftReference<V> {
|
|
|
+
|
|
|
+ public final K key;
|
|
|
+
|
|
|
+ public KRef(K key, V value, ReferenceQueue<V> queue) {
|
|
|
+ super(value, queue);
|
|
|
+ this.key = key;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class TRef<K,V> extends KRef<K,V> {
|
|
|
+
|
|
|
+ public Instant time;
|
|
|
+
|
|
|
+ public TRef(K key, V value, ReferenceQueue<V> queue, Duration ttl) {
|
|
|
+ super(key, value, queue);
|
|
|
+ this.time = Instant.now().plus(ttl);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public V get() {
|
|
|
+ return time.isAfter(Instant.now()) ? null : super.get();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class TRMap<K,V> extends LinkedHashMap<K, TRef<K,V>> implements CacheMap<K,V> {
|
|
|
+
|
|
|
+ protected final int maxsize;
|
|
|
+
|
|
|
+ protected final Duration ttl;
|
|
|
+
|
|
|
+ protected transient final ReferenceQueue<V> queue = new ReferenceQueue<>();
|
|
|
+
|
|
|
+ public TRMap(int maxsize, Duration ttl) {
|
|
|
+ super( Math.min(Math.round(maxsize / 0.75f)+2, 2048), 0.75f, false);
|
|
|
+ this.maxsize = maxsize;
|
|
|
+ this.ttl = ttl;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected boolean removeEldestEntry(Entry<K, TRef<K,V>> eldest) {
|
|
|
+ return this.size() > maxsize || eldest.getValue().time.isAfter(Instant.now());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void release() {
|
|
|
+ synchronized(this) {
|
|
|
+ Reference<? extends V> ref;
|
|
|
+ while ( null != (ref = queue.poll()) ) { remove( asKey(ref) ); }
|
|
|
+
|
|
|
+ Instant now = Instant.now();
|
|
|
+ Iterator<Entry<K, TRef<K, V>>> iterator = entrySet().iterator();
|
|
|
+ while(iterator.hasNext()) {
|
|
|
+ if(iterator.next().getValue().time.isBefore(now)) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ iterator.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void putValue(K key, V value) {
|
|
|
+ put(key, new TRef<>(key, value, queue, ttl));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public V removeValue(Object key) {
|
|
|
+ Reference<V> ref = remove(key);
|
|
|
+ return (ref != null) ? ref.get() : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<K, V> view() {
|
|
|
+ return MapUtils.map(this, v -> v.get());
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ private static class CRMap<K,V> extends LinkedHashMap<K, KRef<K,V>> implements CacheMap<K,V> {
|
|
|
+
|
|
|
+ protected final int maxsize;
|
|
|
+
|
|
|
+ protected transient final ReferenceQueue<V> queue = new ReferenceQueue<>();
|
|
|
+
|
|
|
+ public CRMap(int maxsize) {
|
|
|
+ super( Math.min(Math.round(maxsize / 0.75f)+2, 2048), 0.75f, true);
|
|
|
+ this.maxsize = maxsize;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected boolean removeEldestEntry(Entry<K, KRef<K,V>> eldest) {
|
|
|
+ return this.size() > maxsize;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void release() {
|
|
|
+ synchronized(this) {
|
|
|
+ Reference<? extends V> ref;
|
|
|
+ while ( null != (ref = queue.poll()) ) { remove( asKey(ref) ); }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void putValue(K key, V value) {
|
|
|
+ put(key, new KRef<>(key, value, queue));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public V removeValue(Object key) {
|
|
|
+ Reference<V> ref = remove(key);
|
|
|
+ return (ref != null) ? ref.get() : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<K, V> view() {
|
|
|
+ return MapUtils.map(this, v -> v.get());
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ private interface CacheMap<K,V> {
|
|
|
+
|
|
|
+ Map<K,V> view();
|
|
|
+
|
|
|
+ void release();
|
|
|
+
|
|
|
+ void putValue(K key, V value);
|
|
|
+
|
|
|
+ default V getValue(Object key) {
|
|
|
+ Reference<V> ref = get(key);
|
|
|
+ if(null == ref) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ V var = ref.get();
|
|
|
+ if(null == var) {
|
|
|
+ remove(key);
|
|
|
+ }
|
|
|
+ return var;
|
|
|
+ }
|
|
|
+
|
|
|
+ default V removeValue(Object key) {
|
|
|
+ Reference<V> ref = remove(key);
|
|
|
+ return (ref != null) ? ref.get() : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ void clear();
|
|
|
+
|
|
|
+ int size();
|
|
|
+
|
|
|
+ Reference<V> get(Object key);
|
|
|
+
|
|
|
+ Reference<V> remove(Object key);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class CacheList<TV> extends ArrayDeque<TV> {
|
|
|
+
|
|
|
+ private final int maxsize;
|
|
|
+
|
|
|
+ public CacheList(int maxsize) {
|
|
|
+ this.maxsize = maxsize;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void hold(TV value) {
|
|
|
+ addFirst(value);
|
|
|
+ if (size() > maxsize) { removeLast(); }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|