Java Time-Based Map/Cache with Expiring Keys

Java time-based map/cache with expiring keys

Yes. Google Collections, or Guava as it is named now has something called MapMaker which can do exactly that.

ConcurrentMap<Key, Graph> graphs = new MapMaker()
.concurrencyLevel(4)
.softKeys()
.weakValues()
.maximumSize(10000)
.expiration(10, TimeUnit.MINUTES)
.makeComputingMap(
new Function<Key, Graph>() {
public Graph apply(Key key) {
return createExpensiveGraph(key);
}
});

Update:

As of guava 10.0 (released September 28, 2011) many of these MapMaker methods have been deprecated in favour of the new CacheBuilder:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});

How to Implement CacheMap with automatic expiration of entries?

You have to manage an internal map with the same key. Use your put method to add the new value to your map and also add a value for your internal times' map. You can store a Long as a value, which is the concrete time for that value.

Then, start a new thread in the background that will check all times for all keys in the internal map and remove those that are 'old' entries from both, internal map and your main map.

Here is the code. As I see your Map implements an interface with some methods provided to clear the expired values, I understand you don't need an automatic way to remove expired values. So, the code should be something like:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class CacheMapImpl implements CacheMap<Integer, String> {

private Map<Integer, Long> timesCache = new HashMap<Integer, Long>();
private Map<Integer, String> values = new HashMap<Integer, String>();

/** Time for the elemens to keep alive in the map in milliseconds. */
long timeToLive = 0;

@Override
public void setTimeToLive(long timeToLive) {
this.timeToLive = timeToLive;
}

@Override
public long getTimeToLive() {

return this.timeToLive;
}

@Override
public String put(Integer key, String value) {
values.put(key, value);
timesCache.put(key, System.currentTimeMillis());
return value;
}

@Override
public void clearExpired() {

// Just remove if timeToLive has been set before...
if (timeToLive > 0) {
List<Integer> keysToClear = new ArrayList<Integer>();
long currentTime = System.currentTimeMillis();

// Check what keys to remove
for (Entry<Integer, Long> e : timesCache.entrySet()) {
if ((currentTime - e.getValue().longValue()) > this.timeToLive) {
keysToClear.add(e.getKey());
}
}

// Remove the expired keys
for (Integer key : keysToClear) {
this.timesCache.remove(key);
this.values.remove(key);
}
}

}

@Override
public void clear() {
this.timesCache.clear();
this.values.clear();
}

@Override
public boolean containsKey(Object key) {

return this.values.containsKey(key);
}

@Override
public boolean containsValue(Object value) {

return this.values.containsValue(value);
}

@Override
public String get(Object key) {

return this.values.get(key);
}

@Override
public boolean isEmpty() {

return this.values.isEmpty();
}

@Override
public String remove(Object key) {
String rto = null;
if (containsKey(key)) {
this.values.remove(key);
this.timesCache.remove(key);
rto = key.toString();
}
return rto;
}

@Override
public int size() {

return this.values.size();
}

}

Map auto expire elements that are not accessed

You can use Guava's cache.

It has two options:

  1. expireAfterWrite: Expires the entry after a certain period of time since the the entry was added to the cache; or the most recent replacement of the value
  2. expireAfterAccess: Expires the entry after a certain period of time since the entry was last accessed in the cache.

What you want is number 2.

Java time-based map/cache with expiring keys

Yes. Google Collections, or Guava as it is named now has something called MapMaker which can do exactly that.

ConcurrentMap<Key, Graph> graphs = new MapMaker()
.concurrencyLevel(4)
.softKeys()
.weakValues()
.maximumSize(10000)
.expiration(10, TimeUnit.MINUTES)
.makeComputingMap(
new Function<Key, Graph>() {
public Graph apply(Key key) {
return createExpensiveGraph(key);
}
});

Update:

As of guava 10.0 (released September 28, 2011) many of these MapMaker methods have been deprecated in favour of the new CacheBuilder:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});

Java Guava Cache: how to get entires after expiration

Just useCache#getAllPresent(Iterable) which "Returns a map of the values associated with keys in this cache. The returned map will only contain entries which are already present in the cache."

ImmutableMap<String, String> presentEntries = myCache.getAllPresent(mySet());

Simple Java String cache with expiration possibility

How about creating a Map where the item expires using a thread executor

//Declare your Map and executor service
final Map<String, ScheduledFuture<String>> cacheNames = new HashMap<String, ScheduledFuture<String>>();
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

You can then have a method that adds the cache name to your collection which will remove it after it has expired, in this example its one second. I know it seems like quite a bit of code but it can be quite an elegant solution in just a couple of methods.

ScheduledFuture<String> task = executorService.schedule(new Callable<String>() {
@Override
public String call() {
cacheNames.remove("unique_string");
return "unique_string";
}
}, 1, TimeUnit.SECONDS);
cacheNames.put("unique_string", task);

Expiring Map with signaling capability when element expires

guava supports a callback on eviction:

    Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterAccess(100, TimeUnit.SECONDS)
.removalListener(new RemovalListener<Object, Object>() {
public void onRemoval(RemovalNotification<Object, Object> objectObjectRemovalNotification) {
//do something
}
})
.build();


Related Topics



Leave a reply



Submit