迭代番石榴LoadCache的地图视图条目集是否会重置用于过期的访问时间?

2022-09-03 02:20:28

我有一个使用CacheBuilder创建的LoadingCache<K>V

LoadingCache<K,V> myCache = CacheBuilder.newBuilder()
    .expireAfterAccess(1, TimeUnit.MINUTES)
    .maximumSize(500)
    .build(someCacheLoader);

我需要定期迭代缓存中的所有条目(键和值)。我知道我可以使用LoadingCache#asMap()来完成它,并且:

在前一种情况下:

for (Map.Entry<K, V> entry : myCache.asMap().entrySet()) {
    K key = entry.getKey();
    V value = entry.getValue();

    doSomeWorkOn(key, value);
}

这会更新缓存中每个条目的访问时间吗?我已经非常仔细地阅读了JavaDoc for CacheBuilder#expireAfterAccess(long,TimeUnit),但发现在这种情况下它是模棱两可/不清楚的:

指定在创建条目、最近一次替换其值或上次访问该条目后经过固定持续时间后,应自动从缓存中删除每个条目。访问时间由所有缓存读写操作(包括 和 )重置,但不能由 的集合视图上的操作重置。Cache.asMap().get(Object)Cache.asMap().put(K, V)Cache.asMap

显然,我提到的第二种迭代方式确实重置了访问时间,但我想知道第一种方式的行为是什么。


答案 1

我会这样解释:

(...)但不是通过对 Cache.asMap 的集合视图执行的操作

以引用 、 和 。这些是 的三个集合视图。因此,使用它们不应导致访问。entrySetkeySetvaluesMap

以下是JUnit(+Mockito)测试,显示了每种情况下的行为。通过 或 阻止删除条目 (读取或 中的键也不会阻止删除该值)。阅读使用确实算作一种访问权限,正如文档所指定的那样。entrySetvaluesentrySetkeySetasMap().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());
}

答案 2

推荐