Friday, February 8, 2013

LRUCache in java

The simplest way to create a cache using LinkedHashMap is to extend it. The constructor takes as an argument the maximum number of entries we want a Cache object to hold. The superclass constructor has three arguments: the initial capacity of the map, the load factor, and a boolean argument that tells theLinkedHashMap constructor to keep entries in access order instead of the default insertion order.

The removeEldestEntry() method overrides a default implementation in LinkedHashMap and is where we determine the policy for removing the oldest entry. In this case, we return truewhen the cache has more entries than our defined capacity.

Hit rate is usually expressed as a percentage - a hit rate above 80% is usually pretty good for a cache.We can add a few things to our Cache class to make it possible to monitor performance so that we can tune the cache by setting an optimum size. We need a counter for the number of accesses and another for the number of hits. We will need getter methods to allow us to retrieve those values after the cache has been running for a time, and finally we must override the get() method to update the counts.

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

public class LRUCache extends LinkedHashMap<Object, Object> {

/**
* serialUID
*/
private static final long serialVersionUID = -4412392853140727526L;

private final int capacity;
private AtomicLong accessCount = new AtomicLong(0);
private AtomicLong hitCount = new AtomicLong(0);

/**
* Constructs caching object with access order instead of insertion order
*
* @param capactity
* The capacity of the cache after which the eldest entry will be
* removed
*
*/
public LRUCache(int capacity) {
/**
* passing load factory as 1.2f to avoid resizing of HashMap till it
* reaches its full capacity passing true as accessOrder will ensure
* eldest entry is based on access and not on insertion
* */
super(capacity + 1, 1.2f, true);
this.capacity = capacity;
}

@Override
public Object get(Object key) {
accessCount.getAndIncrement();
if (containsKey(key)) {
hitCount.getAndIncrement();
}
Object value = super.get(key);
return value;
}

protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
return size() > capacity;
}

/**
* Returns the number of times the cache was accessed
*
* @return returns the access count
*/
public long getAccessCount() {
return accessCount.get();
}

/**
* Returns the number of times the entry was found in the cache
*
* @return returns the hit count
*/
public long getHitCount() {
return hitCount.get();
}
}

No comments: