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