diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8cf4617 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.iml +*.xml diff --git a/SoftCache/.gitignore b/SoftCache/.gitignore new file mode 100644 index 0000000..ffd2a4a --- /dev/null +++ b/SoftCache/.gitignore @@ -0,0 +1 @@ +*.idea diff --git a/SoftCache/pom.xml b/SoftCache/pom.xml new file mode 100644 index 0000000..d2a7ca6 --- /dev/null +++ b/SoftCache/pom.xml @@ -0,0 +1,100 @@ + + + + 4.0.0 + + maven + softCache + jar + 1.0 + + A Camel Route + + + UTF-8 + UTF-8 + + + + + + + org.apache.camel + camel-parent + 2.18.3 + import + pom + + + + + + + + org.apache.camel + camel-core + + + + + org.apache.logging.log4j + log4j-api + runtime + + + org.apache.logging.log4j + log4j-core + runtime + + + org.apache.logging.log4j + log4j-slf4j-impl + runtime + + + + + org.apache.camel + camel-test + test + + + + + install + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.1 + + UTF-8 + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.5.0 + + maven.MainApp + false + + + + + + + diff --git a/SoftCache/src/main/java/maven/Cache.java b/SoftCache/src/main/java/maven/Cache.java new file mode 100644 index 0000000..ab5fcb1 --- /dev/null +++ b/SoftCache/src/main/java/maven/Cache.java @@ -0,0 +1,23 @@ +package maven; + +public interface Cache { + /** + * Возвращает соответствующее значение, если оно ещё в кэше, иначе null + */ + V getIfPresent(K key); + + /** + * Сохраняет value по соответствующему ключу key + */ + void put(K key, V value); + + /** + * Удаляет соответствующее ключу key значение + */ + V remove(K key); + + /** + * Очищает кэш + */ + void clear(); +} diff --git a/SoftCache/src/main/java/maven/SoftCacheMap.java b/SoftCache/src/main/java/maven/SoftCacheMap.java new file mode 100644 index 0000000..4b4616c --- /dev/null +++ b/SoftCache/src/main/java/maven/SoftCacheMap.java @@ -0,0 +1,111 @@ +package maven; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; + +public class SoftCacheMap implements Cache { + + private HashMap> cache; + private HashMap, HashSet> subsidiaryMap; + private ReferenceQueue referenceQueue; + private Deque recentlyUsed; + private int maxSize; + private int cleaningFrequency; + private int numOfPuts; + + protected SoftCacheMap(int size, int frequency) { + cache = new HashMap<>(); + /** + * второй hashMap - вспомогательный, используется при удалении из кэша + * тех ссылок, которые попали в referenceQueue. Чтобы не искать все ключи, + * соответствующие одной ссылке, храню Set из таких ключей в качестве значения + * во втором hashMap-e. + */ + subsidiaryMap = new HashMap<>(); + referenceQueue = new ReferenceQueue<>(); + recentlyUsed = new LinkedList<>(); + maxSize = size; + cleaningFrequency = frequency; + numOfPuts = 0; + } + + private void putInQueue(V object) { + if (maxSize > 0) { + if (object != null) { + recentlyUsed.remove(object); + if (recentlyUsed.size() < maxSize) { + recentlyUsed.addLast(object); + } else { + recentlyUsed.addLast(object); + recentlyUsed.removeFirst(); + } + } + } + } + + + private void clean() { + boolean done = false; + while (!done) { + SoftReference reference = (SoftReference) referenceQueue.poll(); + if (reference != null) { + if (subsidiaryMap.containsKey(reference)) { + HashSet localSet = subsidiaryMap.remove(reference); + for (K local : localSet) { + if (reference.get() == null) { + cache.remove(local); + } + } + } + } else { + done = true; + } + } + } + + @Override + public V getIfPresent(K key) { + if (cache.containsKey(key)) { + putInQueue(cache.get(key).get()); + return cache.get(key).get(); + } + return null; + } + + @Override + public void put(K key, V value) { + ++numOfPuts; + SoftReference reference = new SoftReference<>(value, referenceQueue); + cache.put(key, reference); + putInQueue(value); + if (subsidiaryMap.containsKey(reference)) { + subsidiaryMap.get(reference).add(key); + } else { + subsidiaryMap.put(reference, new HashSet<>()); + subsidiaryMap.get(reference).add(key); + } + if (numOfPuts % cleaningFrequency == 0) { + clean(); + } + } + + @Override + public V remove(K key) { + if ((cache.containsKey(key)) && (cache.get(key).get() != null)) { + subsidiaryMap.remove(cache.get(key)); + return cache.remove(key).get(); + } + return null; + } + + @Override + public void clear() { + cache.clear(); + subsidiaryMap.clear(); + } + +} diff --git a/SoftCache/src/main/resources/log4j2.properties b/SoftCache/src/main/resources/log4j2.properties new file mode 100644 index 0000000..328db35 --- /dev/null +++ b/SoftCache/src/main/resources/log4j2.properties @@ -0,0 +1,7 @@ + +appender.out.type = Console +appender.out.name = out +appender.out.layout.type = PatternLayout +appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n +rootLogger.level = INFO +rootLogger.appenderRef.out.ref = out diff --git a/SoftCache/src/test/java/maven/SoftCacheMapTest.java b/SoftCache/src/test/java/maven/SoftCacheMapTest.java new file mode 100644 index 0000000..4cfd06a --- /dev/null +++ b/SoftCache/src/test/java/maven/SoftCacheMapTest.java @@ -0,0 +1,93 @@ +package maven; + +import org.junit.Assert; +import org.junit.Test; +import java.util.ArrayList; +import java.util.Arrays; + +public class SoftCacheMapTest { + + private SoftCacheMap getCache(int size, int frequency) { + return new SoftCacheMap<>(size, frequency); + } + + private void testBefore(int size, int frequency, ArrayList values, + ArrayList expectedBefore) throws Exception { + + String errorMessage = "Bad result"; + SoftCacheMap cache = getCache(size, frequency); + for (int i = 0; i < values.size(); ++i) { + cache.put(i, values.get(i)); + } + for (int i = 0; i < values.size(); ++i) { + Assert.assertEquals(errorMessage, expectedBefore.get(i), cache.getIfPresent(i)); + } + } + + private void testAfter(int size, int frequency, ArrayList values, + ArrayList expectedAfter) { + String errorMessage = "Bad result"; + SoftCacheMap cache = getCache(size, frequency); + for (int i = 0; i < values.size(); ++i) { + cache.put(i, new Integer(values.get(i))); + } + try { + Object[] big = new Object[(int) Runtime.getRuntime().maxMemory()]; + } catch (OutOfMemoryError e) { + // ignore + } + + int counter = 0; + for (int i = 0; i < values.size(); ++i) { + if (cache.getIfPresent(i) != null) { + Assert.assertEquals(errorMessage, expectedAfter.get(counter), cache.getIfPresent(i)); + ++counter; + } + } + } + + @Test + public void testReferenceQueueBefore() throws Exception { + ArrayList values = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5)); + ArrayList expectedBefore = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5)); + testBefore(0, 1, values, expectedBefore); + } + + @Test + public void testReferenceQueueAfter() throws Exception { + ArrayList values = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5)); + testAfter(0, 1, values, null); + } + + @Test + public void testRecentlyUsedQueueAfter0() throws Exception { + ArrayList values = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5)); + ArrayList expectedAfter = new ArrayList<>(Arrays.asList(4, 5)); + testAfter(2, 1, values, expectedAfter); + } + + @Test + public void testRecentlyUsedQueueAfter1() throws Exception { + ArrayList values = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5)); + ArrayList expectedAfter = new ArrayList<>(Arrays.asList(2, 5)); + String errorMessage = "Bad result"; + SoftCacheMap cache = getCache(2, 1); + for (int i = 0; i < values.size(); ++i) { + cache.put(i, new Integer(values.get(i))); + } + cache.getIfPresent(2); + try { + Object[] big = new Object[(int) Runtime.getRuntime().maxMemory()]; + } catch (OutOfMemoryError e) { + // ignore + } + + int counter = 0; + for (int i = 0; i < values.size(); ++i) { + if (cache.getIfPresent(i) != null) { + Assert.assertEquals(errorMessage, expectedAfter.get(counter), cache.getIfPresent(i)); + ++counter; + } + } + } +}