0

I have a system where objects (for the purposes of this question they are immutable) are created based on a request object (could be as simple as a url or a long). They are created with a factory method and not with new.

If an object for a request already exists, requesting a new object would be done more efficiently if instead we can get a reference to the existing instance.

To that end I have created a class, called UniversalCache<K, V> for lack of a better name at this time. It has an LruCache so that an X amount of strong references are kept, and a HashMap<K, SoftReference<V> > to keep track of all the objects that might still be kept alive via other strong references in the system (I'm not relying on a SoftReference keeping the objects from being GC'd).

When a new object is created that is not already in the cache, it is added to the cache along with its key. To search for it in the cache I use the key to get the reference and check if it still has a reference to an object.

The problem I'm having is how to remove these key/reference pairs once the objects get garbage collected. I don't want to go over the whole HashMap searching for references for which poll returns null. Since the referent is not always available, I can't use it to obtain or generate a key back. So I'm extending SoftReference to store the key and use it to remove the pair from the HashMap. Is this a good idea? I have a KeyedSoftReference<K,Rt> that has an additional field of the same type K for the key as the cache (and Rt which ends up being the same as V).

In particular I'd like advice on where to handle the ReferenceQueue (at the moment it's in get) and how to cast the object I get from ReferenceQueue.poll().

This is the code that I have up to now:

    package com.frozenkoi.oss;

    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.lang.ref.SoftReference;
    import java.util.HashMap;

    import android.util.LruCache;

    public class UniversalCache<K, V> {
            private final LruCache<K, V> mStrongCache;
            private final HashMap<K, KeyedSoftReference<K, V> > mSoftCache;
            private final ReferenceQueue<V> mRefQueue;

            private static class KeyedSoftReference<K, Rt> extends SoftReference<Rt>
            {
                    private final K mKey;
                    public KeyedSoftReference(K key, Rt r, ReferenceQueue<? super Rt> q)
                    {
                            super(r, q);
                            mKey = key;
                    }

                    public K getKey()
                    {
                            return mKey;
                    }
            }

            public UniversalCache(int strongCacheMaxItemCount)
            {
                    mStrongCache = new LruCache<K, V>(strongCacheMaxItemCount);
                    mSoftCache = new HashMap<K, KeyedSoftReference<K, V> >();
                    mRefQueue = new ReferenceQueue<V>();
            }

            private void solidify(K key, V value)
            {
                    mStrongCache.put(key, value);
            }

            public void put(K key, V value)
            {
                    solidify(key, value);
                    mSoftCache.put(key, new KeyedSoftReference<K, V>(key, value, mRefQueue));
            }

            public V get(K key)
            {
                    //if it's in Strong container, must also be in soft.
                    //just check in one of them
                    KeyedSoftReference<K,? extends V> tempRef = mSoftCache.get(key);
                    final V tempVal = (null!=tempRef)?tempRef.get():null;

                    V retVal = null;

                    if (null == tempVal)
                    {
                            mSoftCache.remove(key);
                            retVal = tempVal;
                    }
                    else
                    {
                            //if found in LruCache container, must be also in Soft one
                            solidify(key, tempVal);
                            retVal = tempVal;
                    }

                    //remove expired entries
                    while (null != (tempRef = (KeyedSoftReference<K,V>)mRefQueue.poll()))       //Cast
                    {
                            //how to get key from val?
                            K tempKey = tempRef.getKey();
                            mSoftCache.remove(tempKey);
                    }

                    return retVal;
            }

    }
frozenkoi
  • 3,228
  • 22
  • 33
  • I'd *strongly* recommend using, or at least borrowing the exact code from, one of the many other open-source rock-solid cache implementations - rolling your own is pretty much never the right answer, IME. Ehcache may be too heavyweight overall for Android, but you should be able to pick up some usable code from it. – jkraybill Oct 22 '13 at 05:10
  • @jkraybill Do you mean http://ehcache.org ? – frozenkoi Oct 22 '13 at 05:38
  • Yes, among many others. Ehcache is probably the most popular and among the most stable/well-implemented. You may also want to look at Apache Commons Pool, specifically SoftReferenceObjectPool, for ideas. – jkraybill Oct 22 '13 at 06:24

0 Answers0