In the Guava Library, I am confused about why Cache.asMap()
is not consistent with Cache.size()
, unless Cache.cleanUp()
is called.
Cache<Object, Object> cache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.build();
cache.get(...);
...
//After some seconds, all entries are expired.
//cache.asMap() is EMPTY Map, but cache.size() != 0
So my question: Is it bug that Cache.asMap()
is not consistent to Cache.size()
?
Although I notice the javadoc of Cache.size()
is:
/**
* Returns the **approximate** number of entries in this cache.
*/
I can just guess it's related to a concurrent environment.
And what does Cache.cleanUp()
do exactly?
Guava's cache is designed around lock amortization and the
cleanUp
method forces the cache to arrive at a consistent state. TheMap.size()
method is an approximation, but may count entries pending removal due to expiration or reference eviction. The visibility of the approximations in Guava's cache are rarely of much interest to an application, which tends to think of a cache as a transient data store. The different expectations of a cache from a Map led to theasMap
method to allow the cache to be viewed as a map, but disfavor developers perceiving it that way.The implementation details of the cache was covered in the StrangleLoop 2011 conference slides. The design document of
ConcurrentLinkedHashMap
, which Guava's cache was derived from, may also be of interest but describes a slightly different approach.Ben gave a good high-level response. The low-level response is:
asMap()
views have the luxury of traversing each element in the cache, and thus can skip invalid entries which are pending cleanup. On the other hand,size()
is expected to be a quick operation, and it would be silly to traverse the entire cache just to get a more accurate size estimate.The
CacheBuilder
javadocs go into more detail regarding the cleanup that needs to happen in various situations (such asexpireAfterWrite
, in your case).