HashMap with weak values

元气小坏坏 提交于 2019-12-03 23:44:27
Joachim Sauer

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";
           }
       }
   ); 

A WeakReference is added to its ReferenceQueue supplied at the construction time when its reference is collected.

You could poll the ReferenceQueue whenever you access the cache, and hold a HashMap<WeakReference<CustomObject>,Long> to know which entry to remove if a reference is found in the queue.

Alternatively, if the cache is not frequently used, you can watch the queue in a separate thread.

rgrocha

Have you tried android.util.LruCache (its a SDK11 class but it's also in the compatibility package as android.support.v4.util.LruCache). It does not implement java.util.Map but works like a Map and you can define how much memory will it take and it will flush old (unused cached objects by itself).

You could start a "cleanup" - Thread every once in a while. Perhaps if your map size exceeds a threshold but at most every 5 minutes .... something like that.

Keep the cleanup cycles short to not block the main functionality.

I think the best option (if a dependency on Guava is undesirable) would be to use a custom subclass of WeakReference that remembers its ID, so that your cleanup thread can remove the weak values during cleanup of the WeakReferences.

The implementation of the weak reference, with the necessary ReferenceQueue and cleanup thread would look something like this:

class CustomObjectAccess {

    private static final ReferenceQueue<CustomObject> releasedCustomObjects = 
                                                                  new ReferenceQueue<>();

    static {
        Thread cleanupThread = new Thread("CustomObject cleanup thread")                  
            while (true) {
                CustomObjectWeakReference freed = (CustomObjectWeakReference) 
                                CustomObjectWeakReference.releasedCustomObjects.remove();
                cache.remove(freed.id);
            }
        };
        cleanupThread.start();
    }

    private Map<CustomObjectID, CustomObjectWeakReference> cache;

    public CustomObject get(CustomObjectID id) {
        synchronized(this){
            CustomObject result= getFromCache(id);
            if (result==null) {
                result=getObjectFromPersistence(id);
                addToCache(result);
            }
        }
        return result;
    }

    private addToCache(CustomObject co) {
        cache.put(CustomObject.getID(), new CustomObjectWeakReference(co));
    }

    private getFromCache(CustomObjectID id) {
        WeakReference<CustomObject> weak = cache.get(id);
        if (weak != null) {
            return weak.get();
        }
        return null;
    }

    class CustomObjectWeakReference extends WeakReference<CustomObject> {

        private final CustomObjectID id;

        CustomObjectWeakReference(CustomObject co) {
            super(co, releasedCustomObjects);
            this.id = co.getID();
        }
    }
}

I had the need to store tagged weak objects and figured instead of using WeakHashMap<String, T>, I could just use WeakHashMap<T, String> instead.

This is Kotlin, but should apply to Java equally:

abstract class InstanceFactory<T> {
    @Volatile
    private var instances: MutableMap<T, String> = WeakHashMap<T, String>()

    protected fun getOrCreate(tag: String = SINGLETON, creator: () -> T): T =
        findByTag(tag)?.let {
            it
        } ?: synchronized(this) {
            findByTag(tag)?.let {
                it
            } ?: run {
                creator().also {
                    instances[it] = tag
                }
            }
        }

    private fun findByTag(tag: String): T? = instances.entries.find { it.value == tag }?.key

    companion object {
        const val SINGLETON = "singleton"
    }
}

This can be used as follows:

class Thing(private val dependency: Dep) { ... }

class ThingFactory(private val dependency: Dep) : InstanceFactory<Thing>() {

    createInstance(tag: String): Thing = getOrCreate(tag) { Thing(dependency) }

}

Simple singletons can be done like this:

object ThingFactory {
    getInstance(dependency: Dependency): Thing = getOrCreate { Thing(dependency) }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!