Guava, among its many useful things, contains a great implementation of a cache.

A cache, as you know, is an object that manages an association between keys and values, like an hash map, used when the values are generally very “heavy” to calculate and you want to avoid doing that repeatedly. The main difference between a cache and a generic map is that the cache generally has some kind of “eviction” policy, i.e. it only retains a number of the items stored in it, while a map will retain all the values.

From the Guava’s website:

Generally, the Guava caching utilities are applicable whenever:

  • You are willing to spend some memory to improve speed.
  • You expect that keys will sometimes get queried more than once.
  • Your cache will not need to store more data than what would fit in RAM. (Guava caches are local to a single run of your application. They do not store data in files, or on outside servers. If this does not fit your needs, consider a tool like Memcached.)

Usage

Guava’s Cache is quite simple to use:

Value v = myCache.get(key, new Callable<Value>() {
    @Override
    public Value call() throws AnyException {
        return loadTheValueSlowly(key);
    }
});

When asking the cache for a value, you need to pass a Callable that will perform the actual loading of the value whenever it is not found in the cache (which will happen on the first get request, and also for further ones if the value has been evicted).

You can also manually put a value, getIfPresent to request it only if it’s already present, invalidateAll the entries or invalidate a specific key.

Eviction policies

Guava’s caches, as we have mentioned, will remove already existing entries in some situations, to save memory; this is called the eviction policy of the cache. Commonly used policies are:

Let’s see an example of the last one:

    Cache<String, String> cache = CacheBuilder.newBuilder()
       .maximumWeight(100000)
       .weigher(new Weigher<String, String>() {
          public int weigh(String k, String w) {
            return w.length();
          }
        })
       .build();

Loading caches

Often it is not that simple, or readable, to use the version of get which requires a Callable to load the value, but you’d rather pass a Callable at the cache construction time. This can be done with the version of CacheBuilder::build that receives a CacheLoader, which is used more or less in the same way:

    LoadingCache<Key, Item> itemsCache = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .build(
           new CacheLoader<Key, Item>() {
             public Item load(Key key) throws AnyException {
               return loadExpensiveItem(key);
             }
           });

Other details

Guava’s Caches are thread-safe: if a value needs to be loaded for a given key, it will be loaded only once, even if another request for it arrives while it still is loading.

Furthermore, caches can be viewed as a map with the method asMap.

Finally, if necessary, you can ask the CacheLoader to create a cache that will record usages statistics, and then retrieve them with the stats method. This can be quite useful, not only during development, to check the actual efficiency of your caches.