This is an automated email from the ASF dual-hosted git repository. andy pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/jena.git
commit b8b5864a050a2150bbd880d44744b55c9f4c0a07 Author: Andy Seaborne <[email protected]> AuthorDate: Fri Jul 18 09:55:32 2025 +0100 Single-threaded LRU cache based on LinkedHashMap --- .../main/java/org/apache/jena/atlas/lib/Cache.java | 2 +- .../org/apache/jena/atlas/lib/CacheFactory.java | 8 + .../org/apache/jena/atlas/lib/cache/CacheOps.java | 6 +- .../apache/jena/atlas/lib/cache/CachePlainLRU.java | 122 ++++++++++++++ .../java/org/apache/jena/atlas/lib/TestCache.java | 179 ++++++++++----------- .../java/org/apache/jena/atlas/lib/TestCache2.java | 25 ++- 6 files changed, 235 insertions(+), 107 deletions(-) diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java index 46dd53c63c..93437c424f 100644 --- a/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java +++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java @@ -76,7 +76,7 @@ public interface Cache<Key, Value> * @return Returns either the existing value or the calculated value. * If callable is called and returns null, then null is returned. */ - public Value get(Key key, Function<Key, Value> callable) ; + public Value get(Key key, Function<Key, Value> function) ; /** * Insert into the cache diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java index 7ab9f09f60..6cc8ed9a1f 100644 --- a/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java +++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java @@ -88,6 +88,14 @@ public class CacheFactory { return new Cache1<>() ; } + /* + * Create a lightweight LRU cache. + * This cache is not thread-safe. + */ + public static <Key, Value> Cache<Key, Value> createPlainCache(int size) { + return new CachePlainLRU<>(size) ; + } + /** * Create set-cache, rather than a map-cache. * The cache is thread-safe for single operations. diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java index 4a81269cfa..fe6c001188 100644 --- a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java +++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java @@ -46,11 +46,7 @@ class CacheOps { public static <K,V> V getOrFill(Cache<K,V> cache, K key, Function<K,V> function) { V value = cache.getIfPresent(key) ; if ( value == null ) { - try { value = function.apply(key) ; } - catch (RuntimeException ex) { throw ex; } - catch (Exception e) { - throw new AtlasException("Exception on cache fill", e) ; - } + value = function.apply(key) ; if ( value != null ) cache.put(key, value) ; } diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CachePlainLRU.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CachePlainLRU.java new file mode 100644 index 0000000000..a6545cf74f --- /dev/null +++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CachePlainLRU.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.atlas.lib.cache; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import org.apache.jena.atlas.lib.Cache; + +/** + * Light-weight LRU cache based on {@link LinkedHashMap}. + * This cache implementation is for single-threaded use only. + */ +public class CachePlainLRU<K, V> implements Cache<K, V> { + + private final Map<K, V> cache; + private final float loadFactor = 0.75f; + private final float initialCapacityFactory = 0.75f; + + + public CachePlainLRU(final int maxCapacity) { + int initialCapacity = (int)(maxCapacity / initialCapacityFactory + 1); + this.cache = new LinkedHashMap<K, V>(initialCapacity, loadFactor, true) { + @Override + protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { + return size() > maxCapacity; + } + }; + } + + @Override + public boolean containsKey(K key) { + return cache.containsKey(key); + } + + + @Override + public V getIfPresent(K key) { + return cache.get(key); + } + + + @Override + public V get(K key, Function<K, V> function) { + return CacheOps.getOrFill(this, key, function); + } + + @Override + public void put(K key, V thing) { + cache.put(key, thing); + } + + + @Override + public void remove(K key) { + this.remove(key); + } + + + @Override + public Iterator<K> keys() { + return cache.keySet().iterator(); + } + + + @Override + public boolean isEmpty() { + return cache.isEmpty(); + } + + + @Override + public void clear() {} + + + @Override + public long size() { + return 0; + } + + +// +// @Override +// public boolean containsKey(final K key) { +// return cache.containsKey(key); +// } +// +// @Override +// public V get(final K key) { +// return cache.get(key); +// } +// +// @Override +// public void put(final K key, V value) { +// cache.put(key, value); +// } +// +// public long size() { +// return cache.size(); +// } + + + +} diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java index bc1f7a6d94..6458c74f18 100644 --- a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java +++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java @@ -37,114 +37,103 @@ import org.junit.runners.Parameterized.Parameters ; public class TestCache { // Tests do not apply to cache1. - private static interface CacheMaker<K,V> { Cache<K,V> make(int size) ; String name() ; } - - private static CacheMaker<Integer, Integer> simple = - new CacheMaker<Integer, Integer>() - { - @Override - public Cache<Integer, Integer> make(int size) { return CacheFactory.createSimpleCache(size) ; } - @Override - public String name() { return "Simple" ; } - } - ; - - private static CacheMaker<Integer, Integer> standard = - new CacheMaker<Integer, Integer>() - { - @Override - public Cache<Integer, Integer> make(int size) { return CacheFactory.createCache(size) ; } - @Override - public String name() { return "Standard" ; } - } - ; - - @Parameters + private static interface CacheMaker<K,V> { Cache<K,V> make(int size) ; } + + private static CacheMaker<Integer, Integer> oneSlot = (int size)->CacheFactory.createOneSlotCache(); + private static CacheMaker<Integer, Integer> simple = (int size)->CacheFactory.createSimpleCache(size); + private static CacheMaker<Integer, Integer> standard = (int size)->CacheFactory.createCache(size); + private static CacheMaker<Integer, Integer> plainLRU = (int size)->CacheFactory.createCache(size); + + @Parameters(name="{0}") public static Collection<Object[]> cacheMakers() { - return Arrays.asList(new Object[][] { - { simple , 10 } - , { simple , 2 } - , { simple , 1 } - , { standard , 10 } - , { standard , 2 } - , { standard , 1 } - } ) ; + return Arrays.asList(new Object[][] + { { "Simple(10)", simple , 10 } + , { "Simple(2)", simple , 2 } + , { "Simple(1)" , simple ,1 } + , { "Plain(10)", plainLRU , 10 } + , { "Plain(2)", plainLRU , 2 } + , { "Plain(1)" , plainLRU ,1 } + , { "Standard(10)" , standard, 10 } + , { "Standard(2)" , standard, 2 } + , { "Standard(1)" , standard, 1 } + , { "SingleSlot" , oneSlot, 1 } + } ) ; } - Cache<Integer, Integer> cache ; - CacheMaker<Integer,Integer> cacheMaker ; - int size ; - - - public TestCache(CacheMaker<Integer,Integer> cacheMaker, int size) - { - this.cacheMaker = cacheMaker ; - this.size = size ; - + Cache<Integer, Integer> cache; + CacheMaker<Integer, Integer> cacheMaker; + int size; + + public TestCache(String name, CacheMaker<Integer, Integer> cacheMaker, int size) { + this.cacheMaker = cacheMaker; + this.size = size; } - - @Before public void before() { cache = cacheMaker.make(size) ; } - - @Test public void cache_00() - { - assertEquals(0, cache.size()) ; - assertTrue(cache.isEmpty()) ; + + @Before + public void before() { + cache = cacheMaker.make(size); } - - @Test public void cache_01() - { - Integer x = cache.getIfPresent(7) ; - cache.put(7, 7) ; - assertEquals(1, cache.size()) ; - assertNull(x) ; - assertTrue(cache.containsKey(7)) ; - assertEquals(Integer.valueOf(7), cache.getIfPresent(7)) ; + + @Test + public void cache_00() { + assertEquals(0, cache.size()); + assertTrue(cache.isEmpty()); } - - @Test public void cache_02() - { - cache.put(7, 7) ; - cache.put(8, 8) ; + + @Test + public void cache_01() { + Integer x = cache.getIfPresent(7); + cache.put(7, 7); + assertEquals(1, cache.size()); + assertNull(x); + assertTrue(cache.containsKey(7)); + assertEquals(Integer.valueOf(7), cache.getIfPresent(7)); + } + + @Test + public void cache_02() { + cache.put(7, 7); + cache.put(8, 8); // Not true for Cache1. if ( size > 2 ) - assertEquals(2, cache.size()) ; + assertEquals(2, cache.size()); if ( size > 2 ) - assertTrue(cache.containsKey(7)) ; - + assertTrue(cache.containsKey(7)); + if ( size > 2 ) - assertEquals(Integer.valueOf(7), cache.getIfPresent(7)) ; - - assertTrue(cache.containsKey(8)) ; - assertEquals(Integer.valueOf(8), cache.getIfPresent(8)) ; + assertEquals(Integer.valueOf(7), cache.getIfPresent(7)); + + assertTrue(cache.containsKey(8)); + assertEquals(Integer.valueOf(8), cache.getIfPresent(8)); } - - @Test public void cache_03() - { - cache.put(7, 7) ; - Integer x1 = cache.getIfPresent(7) ; - cache.put(7, 18) ; - assertEquals(1, cache.size()) ; - assertEquals(7, x1.intValue()) ; - assertTrue(cache.containsKey(7)) ; - assertEquals(Integer.valueOf(18), cache.getIfPresent(7)) ; + + @Test + public void cache_03() { + cache.put(7, 7); + Integer x1 = cache.getIfPresent(7); + cache.put(7, 18); + assertEquals(1, cache.size()); + assertEquals(7, x1.intValue()); + assertTrue(cache.containsKey(7)); + assertEquals(Integer.valueOf(18), cache.getIfPresent(7)); } - - @Test public void cache_04() - { - cache.clear() ; - cache.put(7, 77) ; - List<Integer> x = Iter.toList(cache.keys()) ; - assertEquals(1, x.size()) ; - assertEquals(Integer.valueOf(7), x.get(0)) ; + + @Test + public void cache_04() { + cache.clear(); + cache.put(7, 77); + List<Integer> x = Iter.toList(cache.keys()); + assertEquals(1, x.size()); + assertEquals(Integer.valueOf(7), x.get(0)); } - - @Test public void cache_05() - { - cache.clear() ; - cache.put(7, 77) ; - cache.clear() ; - assertEquals(0, cache.size()) ; - assertTrue(cache.isEmpty()) ; + + @Test + public void cache_05() { + cache.clear(); + cache.put(7, 77); + cache.clear(); + assertEquals(0, cache.size()); + assertTrue(cache.isEmpty()); } } diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java index f66294016a..cc333fad95 100644 --- a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java +++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java @@ -25,9 +25,8 @@ import org.junit.Test; // Non-parameterized tests public class TestCache2 { - // Cache1 @Test - public void cache_10() { + public void cache_oneSlot() { Cache<Integer, String> cache = CacheFactory.createOneSlotCache(); String str = cache.getIfPresent(1); assertNull(str); @@ -51,7 +50,7 @@ public class TestCache2 { @Test public void cacheGetter_1() { Cache<Integer, String> cache = CacheFactory.createCache(2); - String str = cache.get(1, k->k.toString()); + String str = cache.get(1, k -> k.toString()); assertEquals("1", str); } @@ -59,9 +58,9 @@ public class TestCache2 { @Test public void cacheGetter_2() { Cache<Integer, String> cache = CacheFactory.createCache(2); - String str1 = cache.get(1, k->k.toString()); - String str2 = cache.get(2, k->k.toString()); - String str3 = cache.get(3, k->k.toString()); + String str1 = cache.get(1, k -> k.toString()); + String str2 = cache.get(2, k -> k.toString()); + String str3 = cache.get(3, k -> k.toString()); assertEquals("1", str1); assertEquals("2", str2); assertEquals("3", str3); @@ -70,4 +69,18 @@ public class TestCache2 { assertEquals("10", str1); } + // Cache + getters + @Test + public void cacheGetter_3() { + Cache<Integer, String> cache = CacheFactory.createPlainCache(2); + String str1 = cache.get(1, k -> k.toString()); + String str2 = cache.get(2, k -> k.toString()); + String str3 = cache.get(3, k -> k.toString()); + assertEquals("1", str1); + assertEquals("2", str2); + assertEquals("3", str3); + cache.put(1, "10"); + str1 = cache.getIfPresent(1); + assertEquals("10", str1); + } }
