Java Map with TimeToLive 与每个键/值对相关联

问题描述

最近(在一次采访中)我被要求设计 HashMap,其 TTL 与每个键相关联。我使用下面给出的类似方法完成了它,但在他看来,这不是一个方法,因为这需要在整个地图上进行迭代,如果地图大小以百万为单位,那么这将是一个瓶颈。

有没有更好的方法来做同样的事情?此外,他只担心一个线程会一直在后台运行,尽管下一个 TTL 是几个小时之后。

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);
            }
        }
    }
}

解决方法

最好使用 LinkedHashMap 以便您可以保留插入顺序。实际上 LinkedHashMapHashMap 扩展。如果运行线程是问题所在,那么您可以通过扩展 LinkedHashMap 来创建地图的自定义实现。在类中,覆盖 get 方法。

EDIT :基于 onkar 的评论。最好覆盖 get 而不是 put,因为这会阻止检索过期项目。

public class MyLinkedHashMap<K> extends LinkedHashMap<K,Date> {
    
    private static final long expiryTime = 100000L;
    private long currentOldest = 0L;

    @Override
    public Date get(Object key) {
        long currentTime = new Date().getTime();
        if ((currentOldest > 0L) && (currentOldest + expiryTime) < currentTime) {
            // even the oldest key has not expired.
            return super.get(key);
        }

        Iterator<Map.Entry<K,Date>> iter = this.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<K,Date> entry = iter.next();
            long entryTime = entry.getValue().getTime();
            if (currentTime >= entryTime + expiryTime) {
                iter.remove();
            } else {
                // since this is a linked hash map,order is preserved.
                // All the elements after the current entry came later.
                // So no need to check the remaining elements if the current is not expired.
                currentOldest = entryTime;
                break;
            }
        }

        return super.get(key);
    }
}
,

当你谈论 TTL 并且你想按照它们的 TTL 值的顺序访问它们时,你应该使用 PriorityQueue 或 PriorityBlockingQueue 或 HeapMaps(如果 Java 有一个 HeapMap 实现则是 idk)。

每当您插入一个项目时,它都会在 Collection 中随机排列到正确的排序位置。

因此,如果您只想取出已过期的 TTL,您只需检查/获取,您将首先获得最早过期的,然后继续检查/获取,直到您遇到 TTL 尚未过期的第一个。那就是你停下来的地方。 因为 PriorityQueues 保证(如果你正确地执行了 compareTo 函数)所有的元素总是被排序,所以在点击第一个未过期的条目后,a)该条目将最接近到期​​,b)所有其他元素将有一个稍后到期。队列中的最后一项 - 与您放置它们的顺序无关 - 将是最晚到期的一项。