您是否知道Java Map或类似的标准数据存储在给定的超时后自动清除条目?这意味着老化,旧的过期条目会自动“老化”。
最好是在可通过Maven访问的开源库中?
我知道自己实现这些功能的方法,并且过去曾多次这样做过,所以我不是在这方面寻求建议,而是指向一个好的参考实现。
基于WeakReference的解决方案(如WeakHashMap)不是一个选项,因为我的密钥很可能是非内部字符串,我想要一个不依赖于垃圾收集器的可配置超时。Ehcache也是一个我不想依赖的选项,因为它需要外部配置文件。我正在寻找一个仅限代码的解决方案。
答案 0 :(得分:292)
是。 Google集群或现在命名为Guava的内容名为MapMaker,可以完全实现。
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);
}
});
<强>更新强>
自guava 10.0(2011年9月28日发布)以来,许多这些MapMaker方法已被弃用,转而使用新的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);
}
});
答案 1 :(得分:22)
这是我为同一要求所做的示例实现,并发性很好。可能对某人有用。
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @author Vivekananthan M
*
* @param <K>
* @param <V>
*/
public class WeakConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
private static final long serialVersionUID = 1L;
private Map<K, Long> timeMap = new ConcurrentHashMap<K, Long>();
private long expiryInMillis = 1000;
private static final SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss:SSS");
public WeakConcurrentHashMap() {
initialize();
}
public WeakConcurrentHashMap(long expiryInMillis) {
this.expiryInMillis = expiryInMillis;
initialize();
}
void initialize() {
new CleanerThread().start();
}
@Override
public V put(K key, V value) {
Date date = new Date();
timeMap.put(key, date.getTime());
System.out.println("Inserting : " + sdf.format(date) + " : " + key + " : " + value);
V returnVal = super.put(key, value);
return returnVal;
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for (K key : m.keySet()) {
put(key, m.get(key));
}
}
@Override
public V putIfAbsent(K key, V value) {
if (!containsKey(key))
return put(key, value);
else
return get(key);
}
class CleanerThread extends Thread {
@Override
public void run() {
System.out.println("Initiating Cleaner Thread..");
while (true) {
cleanMap();
try {
Thread.sleep(expiryInMillis / 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void cleanMap() {
long currentTime = new Date().getTime();
for (K key : timeMap.keySet()) {
if (currentTime > (timeMap.get(key) + expiryInMillis)) {
V value = remove(key);
timeMap.remove(key);
System.out.println("Removing : " + sdf.format(new Date()) + " : " + key + " : " + value);
}
}
}
}
}
Git Repo Link (使用监听器实现)
https://github.com/vivekjustthink/WeakConcurrentHashMap
干杯!!
答案 2 :(得分:16)
Apache Commons有Map的装饰器使条目到期:PassiveExpiringMap 它比Guava的缓存更简单。
P.S。小心,它不同步。
答案 3 :(得分:16)
您可以试用自我过期哈希映射的my implementation。此实现不会使用线程来删除过期的条目,而是使用在每次操作时自动清理的DelayQueue。
答案 4 :(得分:3)
Google集合(guava)具有MapMaker,您可以在其中设置时间限制(到期),您可以使用软引用或弱引用,因为您选择使用工厂方法创建您选择的实例。
答案 5 :(得分:3)
听起来ehcache对你想要的东西有点过分,但请注意它不需要外部配置文件。
将配置移动到声明性配置文件中通常是一个好主意(因此,当新安装需要不同的到期时间时,您无需重新编译),但根本不需要,您仍然可以配置它编程。 http://www.ehcache.org/documentation/user-guide/configuration
答案 6 :(得分:2)
你可以试试Expiring Map http://www.java2s.com/Code/Java/Collections-Data-Structure/ExpiringMap.htm 来自Apache MINA项目的课程
答案 7 :(得分:2)
如果有人需要一个简单的东西,以下是一个简单的密钥到期集。它可能很容易转换为地图。
public class CacheSet<K> {
public static final int TIME_OUT = 86400 * 1000;
LinkedHashMap<K, Hit> linkedHashMap = new LinkedHashMap<K, Hit>() {
@Override
protected boolean removeEldestEntry(Map.Entry<K, Hit> eldest) {
final long time = System.currentTimeMillis();
if( time - eldest.getValue().time > TIME_OUT) {
Iterator<Hit> i = values().iterator();
i.next();
do {
i.remove();
} while( i.hasNext() && time - i.next().time > TIME_OUT );
}
return false;
}
};
public boolean putIfNotExists(K key) {
Hit value = linkedHashMap.get(key);
if( value != null ) {
return false;
}
linkedHashMap.put(key, new Hit());
return true;
}
private static class Hit {
final long time;
Hit() {
this.time = System.currentTimeMillis();
}
}
}
答案 8 :(得分:2)
Guava缓存很容易实现。我们可以使用guava缓存在时间基础上过期密钥。我已经阅读了完整的帖子,以下是我学习的关键。
cache = CacheBuilder.newBuilder().refreshAfterWrite(2,TimeUnit.SECONDS).
build(new CacheLoader<String, String>(){
@Override
public String load(String arg0) throws Exception {
// TODO Auto-generated method stub
return addcache(arg0);
}
}
答案 9 :(得分:1)
通常,缓存应该将对象保留一段时间,并且稍后会暴露它们。什么是持有对象的好时机取决于用例。我希望这个东西很简单,没有线程或调度程序。这种方法对我有用。与SoftReference
不同,保证对象在最短的时间内可用。但是,不要留在记忆中until the sun turns into a red giant。
作为使用示例,考虑一个缓慢响应的系统,该系统能够检查最近是否已完成请求,并且在这种情况下不执行所请求的操作两次,即使忙碌的用户多次按下该按钮。但是,如果稍后要求采取相同的行动,则应再次执行。
class Cache<T> {
long avg, count, created, max, min;
Map<T, Long> map = new HashMap<T, Long>();
/**
* @param min minimal time [ns] to hold an object
* @param max maximal time [ns] to hold an object
*/
Cache(long min, long max) {
created = System.nanoTime();
this.min = min;
this.max = max;
avg = (min + max) / 2;
}
boolean add(T e) {
boolean result = map.put(e, Long.valueOf(System.nanoTime())) != null;
onAccess();
return result;
}
boolean contains(Object o) {
boolean result = map.containsKey(o);
onAccess();
return result;
}
private void onAccess() {
count++;
long now = System.nanoTime();
for (Iterator<Entry<T, Long>> it = map.entrySet().iterator(); it.hasNext();) {
long t = it.next().getValue();
if (now > t + min && (now > t + max || now + (now - created) / count > t + avg)) {
it.remove();
}
}
}
}