# 3
I don't mind giving it out. Fill free to modify it or do anything you like with it. But, use this code at your own risk. I will not support it. The code is mostly undocumented.It uses a Timer that every 2 minutes checks the size and cleans out the old entries. You may need to add code to remove the TimerTask from the timer when the Map is no longer needed. I haven't since I've only used this class for static instances. It has two call back methods that can be overridden to find out what elements have been removed: removed() and expiredRemoved().
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ConcurrentCacheMap<K,V> implements ConcurrentMap<K,V> {
static final int DEFAULT_MAX_ENTRIES = 100;
static final double PERCENT_CLEANUP = 0.05;
static final int DEFAULT_SEGMENTS = 16;
static final int CHECK_DURATION = 120 * 1000;
static final Timer timer = new Timer();
protected int maxEntries;
protected ConcurrentHashMap<K,ValueWrapper><V>> map;
/** Creates a new instance of CacheMap */
public ConcurrentCacheMap() {
this(DEFAULT_MAX_ENTRIES, DEFAULT_SEGMENTS);
}
public ConcurrentCacheMap(int maxEntries, int concurrencyLevel) {
map = new ConcurrentHashMap<K,ValueWrapper><V>>((maxEntries+1) * 4 / 3 + 1, 0.75f, concurrencyLevel);
this.maxEntries = maxEntries;
timer.schedule(new CacheCleaner(), CHECK_DURATION, CHECK_DURATION);
}
public boolean replace(K key, V oldValue, V newValue) {
return map.replace(key, new ValueWrapper<V>(oldValue), new ValueWrapper<V>(newValue));
}
public V replace(K key, V value) {
ValueWrapper<V> wrapper = map.replace(key, new ValueWrapper<V>(value));
return checkRemove(key, wrapper);
}
public V putIfAbsent(K key, V value) {
ValueWrapper<V> wrapper = map.putIfAbsent(key, new ValueWrapper<V>(value));
return checkRemove(key, wrapper);
}
public V put(K key, V value) {
ValueWrapper<V> wrapper = map.put(key, new ValueWrapper<V>(value));
return checkRemove(key, wrapper);
}
public void putAll(Map<? extends K,? extends V> t) {
for (Entry<? extends K,? extends V> entry : t.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
public V remove(Object key) {
if (key == null) {
return null;
}
ValueWrapper<V> wrapper = map.remove(key);
return checkRemove(key, wrapper);
}
public boolean remove(Object key, Object value) {
ValueWrapper<Object> wrapper = new ValueWrapper<Object>(value);
boolean rval = map.remove(key, wrapper);
if (rval) {
removed(key, (V)value);
}
return rval;
}
public V get(Object key) {
ValueWrapper<V> wrapper = map.get(key);
//don't increment age call .value
return wrapper == null ? null : wrapper.getValue();
}
public boolean contains(Object value) {
return map.contains(new ValueWrapper<Object>(value));
}
public boolean containsKey(Object key) {
return map.containsKey(key);
}
public boolean containsValue(Object value) {
return map.containsValue(new ValueWrapper<Object>(value));
}
public Collection<V> values() {
return new CollectionValueWrapper();
}
public Enumeration<V> elements() {
return new ValueIter();
}
public Enumeration<K> keys() {
return new KeyIter();
}
public boolean equals(Object obj) {
if (obj instanceof ConcurrentCacheMap) {
return ((ConcurrentCacheMap)obj).map.equals(map);
}
return false;
}
public Set<K> keySet() {
return map.keySet();
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public void clear() {
map.clear();
}
public String toString() {
return map.toString();
}
public int hashCode() {
return map.hashCode();
}
public Set<Entry><K, V>> entrySet() {
return new EntrySetWrapper();
}
final V checkRemove(Object key, ValueWrapper<V> wrapper) {
if (wrapper == null){
return null;
}
V value = wrapper.value;
removed(key, value);
return value;
}
protected void removed(Object key, V value) {
}
protected void expiredRemoved(Object key, V value) {
//System.out.println(value + " " + value.getClass().getName());
}
public int getMaxEntries() {
return maxEntries;
}
public void setMaxEntries(int maxEntries) {
this.maxEntries = maxEntries;
}
static boolean eq(Object o1, Object o2) {
return (o1 == null ? o2 == null : o1.equals(o2));
}
public final static class ValueWrapper<V> {
// I just need approximate values for ageCounter so I don't care if
// ageCounter is concurrently updated and therefore age is skipped or
// duplicated amoung ValueWrapper instances. This should not be a
// problem since I clean groups of old entries.
// For small caches this may be a problem, but then so would be a 2 minute
// cleanup. So if this is a concern then change ageCounter to use an AtomicLong or
// a volatile and make schedule time variable.
static long ageCounter;
public V value;
public long age;
ValueWrapper(V value) {
this.value = value;
age = ageCounter++;
}
public boolean equals(Object o) {
if (!(o instanceof ValueWrapper)) {
return false;
}
return eq(value,((ValueWrapper)o).value);
}
public int hashCode() {
return value.hashCode();
}
public V getValue() {
age = ageCounter++;
return value;
}
public String toString() {
return value == null ? "null " : value.toString();
}
}
abstract class Iter {
Iterator<Entry><K,ValueWrapper><V>>> iter;
Entry<K,ValueWrapper><V>> current;
Iter() {
iter = map.entrySet().iterator();
}
public void remove() {
ConcurrentCacheMap.this.remove(current.getKey());
}
public boolean hasMoreElements() {
return iter.hasNext();
}
public boolean hasNext() {
return iter.hasNext();
}
final Entry<K,ValueWrapper><V>> advance() {
return (current = iter.next());
}
}
final class KeyIter extends Iter implements Iterator<K>, Enumeration<K> {
public K nextElement() { return next(); }
public K next() { return advance().getKey();}
}
final class ValueIter extends Iter implements Iterator<V>, Enumeration<V> {
public V nextElement() { return next(); }
public V next() {
ValueWrapper<V> valueWrapper = advance().getValue();
return valueWrapper == null ? null : valueWrapper.value;
}
}
final class EntryIter extends Iter implements Iterator<Entry><K, V>>, Enumeration<Entry><K, V>> {
public Entry<K, V> nextElement() { return next(); }
public Entry<K, V> next() { return new SimpleEntry<K,V>(advance());}
}
final class CollectionValueWrapper extends AbstractCollection<V> {
public Iterator<V> iterator() {
return new ValueIter();
}
public int size() {
return ConcurrentCacheMap.this.size();
}
public boolean contains(Object o) {
return ConcurrentCacheMap.this.containsValue(o);
}
public void clear() {
ConcurrentCacheMap.this.clear();
}
public Object[] toArray() {
Collection<V> c = new ArrayList<V>();
for (Iterator<V> i = iterator(); i.hasNext(); ) {
c.add(i.next());
}
return c.toArray();
}
public <T> T[] toArray(T[] a) {
Collection<V> c = new ArrayList<V>();
for (Iterator<V> i = iterator(); i.hasNext(); ) {
c.add(i.next());
}
return c.toArray(a);
}
}
final class EntrySetWrapper extends AbstractSet<Entry><K, V>> {
public Iterator<Entry><K, V>> iterator() {
return new EntryIter();
}
public boolean remove(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry<K,V> entry = (Map.Entry<K,V>)o;
return ConcurrentCacheMap.this.remove(entry.getKey(), entry.getValue());
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry<K,V> e = (Map.Entry<K,V>)o;
V v = ConcurrentCacheMap.this.get(e.getKey());
return v != null && v.equals(e.getValue());
}
public int size() {
return ConcurrentCacheMap.this.size();
}
public void clear() {
ConcurrentCacheMap.this.clear();
}
}
final static class SimpleEntry<K,V> implements Entry<K,V> {
Entry<K, ValueWrapper><V>> entry;
SimpleEntry(Entry<K, ValueWrapper><V>> entry) {
this.entry = entry;
}
public V setValue(V value) {
//don't increment age call .value
return entry.setValue(new ValueWrapper<V>(value)).value;
}
public K getKey() {
return entry.getKey();
}
public V getValue() {
ValueWrapper<V> valueWrapper = entry.getValue();
return valueWrapper == null ? null : valueWrapper.value;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Entry e = (Entry)o;
return eq(getKey(), e.getKey()) && eq(getValue(), e.getValue());
}
public int hashCode() {
K key = getKey();
V value = getValue();
return ((key == null) ? 0 :key.hashCode()) ^
((value == null)? 0 : value.hashCode());
}
public String toString() {
return getKey() + "=" + getValue();
}
}
final class CacheCleaner extends TimerTask {
public void run() {
try {
clean();
} catch (Throwable th) {
th.printStackTrace();
}
}
}
public void clean() {
int maxEntries = getMaxEntries();
int size = size();
if (size > maxEntries) {
int removeCount = (int)(maxEntries * PERCENT_CLEANUP) + (size - maxEntries);
System.out.println("Starting to clean " + removeCount + " old items from cache(" + maxEntries +") with " + size + " items.");
long start = System.currentTimeMillis();
//Size can and will likely change during coping to an array inside of toArray
//and and new array will likely be need to be created. So to prevent three
//large arrays from being allocated. Just allocate an empty array for type purposes.
Entry[] allItem = new Entry[0];
allItem = map.entrySet().toArray(allItem);
Arrays.sort(allItem, new EntrySetComparator<K,V>());
int upper = allItem.length;
for (int i=0; i<removeCount; i++) {
Entry><K, ValueWrapper><V>> entry = (Entry<K, ValueWrapper><V>>)allItem[i];
ValueWrapper<V> valueWrapper = entry.getValue();
remove(entry.getKey());
if (valueWrapper != null) {
expiredRemoved(entry.getKey(), valueWrapper.value);
}
}
System.out.println("Cache(" + maxEntries +") took " + (System.currentTimeMillis() - start) + " ms to clean " + removeCount + " items");
}
}
static final class EntrySetComparator<K,V> implements Comparator<Entry><K, ValueWrapper><V>>> {
public int compare(Entry<K, ValueWrapper><V>> o1, Entry<K, ValueWrapper><V>> o2) {
ValueWrapper<V> valueWrapper1 = o1.getValue();
long age1 = valueWrapper1 != null ?
valueWrapper1.age :
-1;
ValueWrapper<V> valueWrapper2 = o2.getValue();
long age2 = valueWrapper2 != null ?
valueWrapper2.age :
-1;
int rval= age1 > age2 ?
1 :
age1 == age2 ?
0 :
-1;
return rval;
}
}
//perform a simple santity test.
public static void main(String ...args) {
final ConcurrentCacheMap<Integer,String> cache = new ConcurrentCacheMap<Integer,String>(300000,16);
new Thread() {
public void run() {
int negCounter = -1;
for (;;) {
cache.put(negCounter, Integer.toString(negCounter));
negCounter--;
try {
Thread.sleep(10);
} catch (InterruptedException ex){
Thread.currentThread().interrupt();
return;
}
}
}
}.start();
int counter = 0;
for (int i=0; i<301000; i++) {
counter++;
cache.put(counter,Integer.toString(counter));
}
for (int i=0; i<20; i++) {
cache.clean();
for (int j=0; j<16000; j++) {
counter++;
cache.put(counter,Integer.toString(counter));
}
}
System.exit(0);
}
}