HashMap with weak values

Addev picture Addev · Nov 16, 2012 · Viewed 8.7k times · Source

I'm implementing a cache for Objects stored persistently. The idea is:

  • Method getObjectFromPersistence(long id); ///Takes about 3 seconds
  • Method getObjectFromCache(long id) //Instantly

And have a method: getObject(long id) with the following pseudocode:

synchronized(this){
    CustomObject result= getObjectFromCache(id)
    if (result==null){
       result=getObjectFromPersistence(id);
       addToCache(result);
    }
    return result;
}

But I need to allow the CustomObject to be collected by the garbage collector. Until now I was using an HashMap<Long,WeakReference<CustomObject> for the implementation. The problem is that over the time the HashMap becomes filled of empty WeakReferences.

I've checked WeakHashMap but there the keys are weak (and the values are still strong references) so having the longs with WeakReferences have no sense.

Whats the best solution for solving this problem? Is there some "inverse WeakHashMap" or something similar?

Thanks

Answer

Joachim Sauer picture Joachim Sauer · Nov 16, 2012

You can use the Guava MapMaker for this:

ConcurrentMap<Long, CustomObject> graphs = new MapMaker()
   .weakValues()
   .makeMap();

You can even include the computation part by replacing makeMap() with this:

   .makeComputingMap(
       new Function<Long, CustomObject>() {
         public CustomObject apply(Long id) {
           return getObjectFromPersistence(id);
         }
       });

Since what you are writing looks a lot like a cache, the newer, more specialized Cache (built via a CacheBuilder) might be even more relevant to you. It doesn't implement the Map interface directly, but provides even more controls that you might want for a cache.

You can refer to this for a detailed how to work for CacheBuilder and here is an example for fast access:

LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
   .maximumSize(100)
   .expireAfterWrite(10, TimeUnit.MINUTES)
   .build(
       new CacheLoader<Integer, String>() {
           @Override
           public String load(Integer id) throws Exception {
               return "value";
           }
       }
   );