自*第一次*插入以来,Java中是否有一个过期映射在一段时间后使元素过期?

Ali*_*aka 4 java caching dictionary expirationhandler

我尝试查看缓存机制,例如 Guava 的Cache. 它们的有效期仅自上次更新以来。

我正在寻找的是一种数据结构,它存储密钥并在第一次插入后经过一段时间后清理密钥。我正计划将该值设为某个计数器。

一个场景可能是一个沉默的工人,他第一次做一些工作,但在一段时间内保持沉默 - 即使需要工作。如果在到期时间过后要求工作,他将完成工作。

知道这样的数据结构吗?谢谢。

Jus*_*ano 8

为此,有几个选项。

被动移除

如果不需要在过期键一过期或按设定的时间间隔清除过期的键(即当键过期或在某个设定的时间间隔内不需要删除键),那么来自 Apache Commons Collections 的PassiveExpiringMap是一个不错的选择。尝试访问此映射中的密钥时,将检查该密钥的生存时间 (TTL)(所有密钥具有相同的 TTL),如果该密钥已过期,则将其从映射中删除并null返回。这个数据结构并不能拥有有效的清理机制,使过期当对应于该键的TTL已过期后,他们正在访问的条目只删除。

缓存

如果需要更多基于缓存的功能(例如最大缓存容量和添加/删除侦听),Google Guava 提供了CacheBuilder类。这个类比 Apache Commons 替代方案更复杂,但它也提供了更多的功能。如果这旨在用于更多基于缓存的应用程序,那么这种权衡可能是值得的。

螺纹拆卸

如果需要主动删除过期密钥,则可以生成一个单独的线程来负责删除过期密钥。在查看可能的实现结构之前,应该注意这种方法的性能可能低于上述替代方案。除了启动线程的开销外,线程可能会导致与访问地图的客户端发生用。例如,如果客户端想要访问一个键并且清理线程当前正在删除过期的键,则客户端将阻塞(如果使用同步)或具有不同的映射视图(包含哪些键值对)在地图中)如果采用了某种并发机制。

话虽如此,使用这种方法很复杂,因为它需要将 TTL 与密钥一起存储。一种方法是创建一个ExpiringKey,例如(每个键可以有自己的 TTL;即使所有键最终都具有相同的 TTL 值,这种技术不需要创建Map 装饰器Map接口的其他实现) :

public class ExpiringKey<T> {

    private final T key;
    private final long expirationTimestamp;

    public ExpiringKey(T key, long ttlInMillis) {
        this.key = key;
        expirationTimestamp = System.currentTimeMillis() + ttlInMillis;
    }

    public T getKey() {
        return key;
    }

    public boolean isExpired() {
        return System.currentTimeMillis() > expirationTimestamp;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在地图的类型将Map<ExpiringKey<K>, V>具有一些特定的KV类型值。可以使用Runnable类似于以下内容的来表示后台线程:

public class ExpiredKeyRemover implements Runnable {

    private final Map<ExpiringKey<?>, ?> map;

    public ExpiredKeyRemover(Map<ExpiringKey<?>, ?> map) {
        this.map = map;
    }

    @Override
    public void run() {
        Iterator<ExpiringKey<?>> it = map.keySet().iterator();
        while (it.hasNext()) {
            if (it.next().isExpired()) {
                it.remove();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后Runnable可以启动它,以便它使用 aScheduledExecutorService以固定间隔执行,如下所示(这将每 5 秒清理一次地图):

Map<ExpiringKey<K>, V> myMap = // ...

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(new ExpiredKeyRemover(myMap), 0, 5, TimeUnit.SECONDS);
Run Code Online (Sandbox Code Playgroud)

需要注意的是,Map用于的实现myMap必须同步或允许并发访问。并发Map实现的挑战在于,如果清理线程没有完成删除其他键(即使它已经删除了所需的键),它ExpiredKeyRemover可能会看到与客户端不同的映射视图,并且可能会将过期的键返回给客户端。 /expired 密钥,因为其更改可能尚未提交)。此外,上述去除密钥的代码可以使用流来实现,但上述代码仅用于说明逻辑,而不是提供高性能实现。

希望有帮助。