HashMap with weak values

前端 未结 7 2367
悲&欢浪女
悲&欢浪女 2021-02-18 14:03

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

  • Method getObjectFromPersistence(long id); ///Takes about 3 seconds
  • M
相关标签:
7条回答
  • 2021-02-18 14:17

    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";
               }
           }
       ); 
    
    0 讨论(0)
  • 2021-02-18 14:17

    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).

    0 讨论(0)
  • 2021-02-18 14:22

    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.

    0 讨论(0)
  • 2021-02-18 14:22

    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) }
    }
    
    0 讨论(0)
  • 2021-02-18 14:26

    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.

    0 讨论(0)
  • 2021-02-18 14:31

    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();
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题