NavigableMap
I often find myself writing time-based caches. Something like storing the last X hours of ticks from a data feed. Most teams I join usually have a utility class to help with such tasks. In a recent move I joined a team that didn't have such a utility, but did have a need for such a cache. Before coding, I thought I do a quick check to see if anything new had come along to help. At this point I discovered the Java 6 NavigableMap interface.
NavigableMap defines a type of sorted map with handy methods for obtaining submaps. So to get a map of the entries greater than a particular value use the tailMap method. The corresponding method for the submap less than a value is headMap. These methods return a view on the original map. So changes to the submap are reflected in the original map. The JDK provides concurrent and non-concurrent implementations of the interface.
This is very useful for a time-based cache. Create a NavigableMap sorted on the time (I used the epoch milliseconds returned by System.currentTimeMillis()) then tailMap returns the entries younger than a given time, and headMap those older. So to trim the cache of entries older than a certain time use headMap(System.currentTimeMillis() - maxAge).clear(). So easy!
Here is a complete time-based cache class for use as an example.
public class TimeSeriesCache<V> {
private static final int MILLIS_IN_MINUTE = 60 * 1000;
private static final int CLEAR_OLD_PERIOD_MINUTES = 1;
private final NavigableMap<Long, V> series = new ConcurrentSkipListMap<Long, V>();
private final Set<TickListener<K, Long, V>> subscribers;
public TimeSeriesCache(int maxAgeMinutes) {
if (maxAgeMinutes > 0) {
final int maxAge = maxAgeMinutes * MILLIS_IN_MINUTE;
Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(
new Runnable() {
@SuppressWarnings("boxing")
@Override public void run() {
series.headMap(System.currentTimeMillis() - maxAge).clear();
}
}, CLEAR_OLD_PERIOD_MINUTES, CLEAR_OLD_PERIOD_MINUTES, TimeUnit.MINUTES);
}
}
public Collection<V> getSeriesAfter(Long fromTimestamp) {
return series.tailMap(fromTimestamp).values();
}
public void add(V value) {
series.put(System.currentTimeMillis(), value);
}
public void add(Long timestamp, V value) {
series.put(timestamp, value);
}
}