diff --git a/src/main/java/de/joshicodes/rja/cache/Cache.java b/src/main/java/de/joshicodes/rja/cache/Cache.java index fb0774b..dd415a4 100644 --- a/src/main/java/de/joshicodes/rja/cache/Cache.java +++ b/src/main/java/de/joshicodes/rja/cache/Cache.java @@ -1,8 +1,6 @@ package de.joshicodes.rja.cache; import java.util.HashMap; -import java.util.Timer; -import java.util.TimerTask; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Stream; @@ -10,52 +8,39 @@ public class Cache { public static final long DEFAULT_LIFESPAN = TimeUnit.HOURS.toMillis(1); + public static final int DEFAULT_MAX_SIZE = 1000; + public static final long DEFAULT_CLEAR_INTERVAL = TimeUnit.MINUTES.toMillis(1); // 1 Minute + private final int maxSize; private final HashMap list; - - private Timer timer; + private long lastClear = System.currentTimeMillis(); public Cache() { - list = new HashMap<>(); - } - - /** - * Schedules a task to clear expired entries - * @param delay Delay between each run - * @param unit Unit of the delay - */ - public void scheduleClearExpired(long delay, TimeUnit unit) { - if(timer != null) timer.cancel(); - timer = new Timer(); - timer.scheduleAtFixedRate( - new TimerTask() { - @Override - public void run() { - clearExpired(); - } - }, - unit.toMillis(delay), - unit.toMillis(delay) - ); + this(DEFAULT_MAX_SIZE); } - /** - * Cancels the task to clear expired entries - * If no task is scheduled, nothing happens - * @see #scheduleClearExpired(long, TimeUnit) - */ - public void cancelClearExpired() { - if(timer != null) timer.cancel(); + public Cache(final int maxSize) { + this.maxSize = maxSize; + list = new HashMap<>(); } /** * Clears all expired entries one time */ public void clearExpired() { + if ((System.currentTimeMillis() - lastClear) < DEFAULT_CLEAR_INTERVAL) return; list.keySet().stream().filter(t -> list.get(t) < System.currentTimeMillis()).forEach(list::remove); + lastClear = System.currentTimeMillis(); } public void add(T t, long lifespan) { + if(list.size() >= maxSize) { + clearExpired(); // Clear expired entries + if(list.size() >= maxSize) { + // If the cache is still full, remove the first entry + list.remove(list.keySet().stream().findFirst().orElse(null)); + } + } list.put(t, System.currentTimeMillis() + lifespan); } @@ -78,7 +63,7 @@ public T get(T t) { if(list.get(t) >= System.currentTimeMillis()) { return t; } else { - list.remove(t); + clearExpired(); // Clear expired entries } } return null; @@ -93,9 +78,11 @@ public void clear() { /** * Returns a stream of all objects in the cache - * @return + * Also clears all expired entries + * @return Stream of all objects in the cache */ public Stream stream() { + clearExpired(); return list.keySet().stream(); } @@ -105,6 +92,7 @@ public Stream stream() { * @return Object if it is in the cache and not expired, null otherwise */ public T getIf(Predicate predicate) { + clearExpired(); return list.keySet().stream().filter(predicate).findFirst().orElse(null); } @@ -112,7 +100,7 @@ public boolean contains(T t) { if(list.containsKey(t)) { if(list.get(t) >= System.currentTimeMillis()) { return true; - } else list.remove(t); // Remove if expired + } else clearExpired(); } return false; } @@ -121,6 +109,14 @@ public boolean containsIf(Predicate predicate) { return list.keySet().stream().anyMatch(predicate); } + /** + * Returns the maximum size of the cache + * @return Maximum size of the cache + */ + public int getMaxSize() { + return maxSize; + } + /** * Returns the HashMap of all objects and their expiration time * To just get the objects, use {@link #stream()} diff --git a/src/main/java/de/joshicodes/rja/cache/CacheMap.java b/src/main/java/de/joshicodes/rja/cache/CacheMap.java index 307a708..ea309cc 100644 --- a/src/main/java/de/joshicodes/rja/cache/CacheMap.java +++ b/src/main/java/de/joshicodes/rja/cache/CacheMap.java @@ -8,17 +8,43 @@ public class CacheMap { + private final int maxSize; private final TrippleMap map; + private long lastClear = System.currentTimeMillis(); + public CacheMap() { + this(Cache.DEFAULT_MAX_SIZE); + } + + public CacheMap(final int maxSize) { + this.maxSize = maxSize; map = new TrippleMap<>(); } + /** + * Clears all expired entries one time + */ + public void clearExpired() { + if((System.currentTimeMillis() - lastClear) < Cache.DEFAULT_CLEAR_INTERVAL) return; + map.keySet().stream().filter(t -> map.get(t).getSecond() < System.currentTimeMillis()).forEach(map::remove); + lastClear = System.currentTimeMillis(); + } + + public void put(K key, T t) { put(key, t, Cache.DEFAULT_LIFESPAN); } public void put(K key, T t, long lifespan) { + if(map.size() >= maxSize) { + // Clear expired entries + clearExpired(); + // If the cache is still full, remove the first entry + if(map.size() >= maxSize) { + map.remove(map.keySet().iterator().next()); + } + } map.put(key, t, System.currentTimeMillis() + lifespan); } @@ -26,7 +52,7 @@ public T get(K key) { Pair pair = map.get(key); if(pair == null) return null; if(pair.getSecond() < System.currentTimeMillis()) { - map.remove(key); + clearExpired(); return null; } return pair.getFirst(); @@ -41,22 +67,27 @@ public boolean containsKey(K key) { } public boolean containsValue(T t) { + clearExpired(); return getSimpleMap().containsValue(t); } public boolean containsFirst(K key, T t) { + clearExpired(); return map.containsFirst(key, t); } public boolean containsSecond(K key, Long lifespan) { + clearExpired(); return map.containsSecond(key, lifespan); } public boolean containsIf(Predicate predicate) { + clearExpired(); return map.getMap().keySet().stream().anyMatch(predicate); } public T getIf(Predicate predicate) { + clearExpired(); return map.getMap().entrySet().stream().filter(entry -> predicate.test(entry.getKey())).findFirst().map(entry -> entry.getValue().getFirst()).orElse(null); } @@ -70,6 +101,10 @@ public HashMap getSimpleMap() { return simpleMap; } + public int getMaxSize() { + return maxSize; + } + public TrippleMap getMap() { return map; } diff --git a/src/main/java/de/joshicodes/rja/util/TrippleMap.java b/src/main/java/de/joshicodes/rja/util/TrippleMap.java index 0b34133..f1d641b 100644 --- a/src/main/java/de/joshicodes/rja/util/TrippleMap.java +++ b/src/main/java/de/joshicodes/rja/util/TrippleMap.java @@ -1,6 +1,7 @@ package de.joshicodes.rja.util; import java.util.HashMap; +import java.util.Set; /** * If you just need a simple Map use {@link java.util.HashMap} or {@link java.util.TreeMap}. @@ -60,6 +61,14 @@ public void clear() { map.clear(); } + public int size() { + return map.size(); + } + + public Set keySet() { + return map.keySet(); + } + public Pair get(K key) { return map.get(key); }