我有一个使用LoadingCache<K,V>
创建的CacheBuilder
:
LoadingCache<K,V> myCache = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.maximumSize(500)
.build(someCacheLoader);
我需要定期迭代缓存中的所有条目(键和值)。我知道我可以使用LoadingCache#asMap()
实现这一点,并且:
Map#entrySet()
,或者Map#keySet()
,并明确地Map#get(Object)
值。在前一种情况下:
for (Map.Entry<K, V> entry : myCache.asMap().entrySet()) {
K key = entry.getKey();
V value = entry.getValue();
doSomeWorkOn(key, value);
}
这会更新缓存中每个条目的访问时间吗?我已经非常仔细地阅读了CacheBuilder#expireAfterAccess(long, TimeUnit)
的JavaDoc,但在这种情况下发现它是模棱两可的/不清楚的:
指定在创建条目,最近替换其值或上次访问后经过固定持续时间后,应自动从缓存中删除每个条目。所有缓存读写操作(包括
Cache.asMap().get(Object)
和Cache.asMap().put(K, V)
)都会重置访问时间,但不会通过Cache.asMap
的collection-views上的操作重置访问时间。
显然,我提到的第二种迭代方式确实会重置访问时间,但我想知道第一种方式是什么行为。
我会解释这个:
(...)但不是通过Cache.asMap的集合视图上的操作
指的是entrySet
,keySet
和values
。这些是Map
的三个集合视图。因此,使用它们不应导致访问。
以下是JUnit(+ Mockito)测试,显示每种情况下的行为。通过entrySet
或values
读取值不会阻止条目被删除(也不会从entrySet
或keySet
读取密钥)。正如文档所指定的那样,使用asMap().get()
进行读取会被视为访问权限。
private Ticker ticker = Mockito.mock(Ticker.class);
@SuppressWarnings({"unchecked"})
private RemovalListener<String, String> removalListener = Mockito.mock(RemovalListener.class);
private Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterAccess(5, TimeUnit.SECONDS)
.removalListener(removalListener)
.ticker(ticker)
.build();
entrySet
@Test
public void testEntrySetAccessDoesNotCountAsAccess() {
//write
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(0));
cache.put("foo", "bar");
//read
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(4));
cache.asMap().entrySet().iterator().next().getValue();
cache.asMap().entrySet().iterator().next().getKey();
//maintenance
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(6));
cache.cleanUp();
verify(removalListener).onRemoval(Mockito.<RemovalNotification<String,String>>any());
}
keySet
@Test
public void testKeySetAccessDoesNotCountAsAccess() {
//write
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(0));
cache.put("foo", "bar");
//read
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(4));
cache.asMap().keySet().iterator().next();
//maintenance
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(6));
cache.cleanUp();
verify(removalListener).onRemoval(Mockito.<RemovalNotification<String,String>>any());
}
values
@Test
public void testValuesAccessDoesNotCountAsAccess() {
//write
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(0));
cache.put("foo", "bar");
//read
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(4));
cache.asMap().values().iterator().next();
//maintenance
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(6));
cache.cleanUp();
verify(removalListener).onRemoval(Mockito.<RemovalNotification<String,String>>any());
}
asMap().get()
@Test
public void testMapGetAccessCountsAsAccess() {
//write
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(0));
cache.put("foo", "bar");
//read
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(4));
cache.asMap().get("foo");
//maintenance
when(ticker.read()).thenReturn(TimeUnit.SECONDS.toNanos(6));
cache.cleanUp();
verify(removalListener, never()).onRemoval(Mockito.<RemovalNotification<String,String>>any());
}