【问题标题】:Java Map with TimeToLive Associated with each key/value pairJava Map with TimeToLive 与每个键/值对相关联
【发布时间】:2021-03-13 15:23:06
【问题描述】:

最近我被要求(在一次采访中)设计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);
            }
        }
    }
}

【问题讨论】:

  • 您可以使用 'timeQueue` 存储密钥、时间戳对,而不是 timeMap。继续删除密钥,直到找到超时密钥。您不必遍历其余的键。
  • 他们要求你明确使用Hashmap 而不是LinkedHashMap ?

标签: java hashmap ttl


【解决方案1】:

扩展 HashMap 及其 Entry 类并为到期时间添加新条目并覆盖 get 方法以仅在到期时间过去时返回值,即 expiryTime

【讨论】:

    【解决方案2】:

    最好使用LinkedHashMap,这样您就可以保留插入顺序。事实上LinkedHashMap 扩展自HashMap。如果运行线程是问题,那么您可以通过扩展LinkedHashMap 创建地图的自定义实现。在类中,重写 get 方法。

    编辑:基于 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);
        }
    }
    

    【讨论】:

    • 在 else 子句中加入 break。如果当前条目已过期,那么您也想检查下一个条目
    • @onkarruikar 已更新。感谢您指出。我以为我在if 中放了一个continue
    • 另外,在 get 方法中执行此操作,而不是 put。您不想要过期的值。这样您就不必按照 OP 中的要求保持线程运行。
    • @onkarruikar 也做了另一个调整
    【解决方案3】:

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

    每当您插入一个项目时,它都会与 Collection 一起被打乱到正确的排序位置。

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

    【讨论】:

      猜你喜欢
      • 2011-08-20
      • 1970-01-01
      • 1970-01-01
      • 2021-06-16
      • 1970-01-01
      • 2017-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多