从HashMap中删除最旧的对象以达到一定的大小?

时间:2016-12-16 12:54:16

标签: java hashmap java-8 java-stream

我在Java中有一个哈希映射,我需要限制其大小(50000的顺序)。但我应该只删除最旧的项目。项目的时间戳存储在条目对象的字段中:

Map<String, MyModel> snapshot = new  HashMap<>();

public class MyModel { 
    private ZonedDateTime createdAt;
    // other fields...
}

我也按时间戳顺序将它们插入到地图中。

完成这种删除最旧条目的最有效方法是什么?请注意,时间“阈值”未知,只有所需的最终地图大小。

4 个答案:

答案 0 :(得分:20)

HashMap没有&#34;最老的&#34;,它没有&#34;第一个&#34;,它没有订单

另一方面,LinkedHashMap就是为了这个而设计的,它在条目之间保持一个双向链表,所以保持它们的插入顺序,它还提供removeEldestEntry方法:

public static void main(final String args[]) throws Exception {
    final int maxSize = 4;
    final LinkedHashMap<String, String> cache = new LinkedHashMap<String, String>() {
        @Override
        protected boolean removeEldestEntry(final Map.Entry eldest) {
            return size() > maxSize;
        }
    };

    cache.put("A", "A");
    System.out.println(cache);
    cache.put("B", "A");
    System.out.println(cache);
    cache.put("C", "A");
    System.out.println(cache);
    cache.put("D", "A");
    System.out.println(cache);
    cache.put("E", "A");
    System.out.println(cache);
    cache.put("F", "A");
    System.out.println(cache);
    cache.put("G", "A");
}

输出:

{A=A}
{A=A, B=A}
{A=A, B=A, C=A}
{A=A, B=A, C=A, D=A}
{B=A, C=A, D=A, E=A}
{C=A, D=A, E=A, F=A}

大健康警告

  

请注意,此实现不是synchronized。如果多个线程同时访问链接的哈希映射,并且至少有一个线程在结构上修改了映射,那么它必须在外部synchronized。这通常由synchronizing在一些自然封装地图的对象上完成。如果不存在这样的对象,那么地图应该被&#34;包裹&#34;使用Collections.synchronizedMap方法。这最好在创建时完成,以防止意外地不同步访问地图:

     

Map m = Collections.synchronizedMap(new LinkedHashMap(...));

<子> LinkedHashMap JavaDoc

答案 1 :(得分:0)

简单地说:然后HashMap不会这样做。除了显而易见的:你已经迭代所有值,检查该属性;然后决定你要删除哪些键。

换句话说:HashMap只有一个职责:将键映射到值。它不关心插入顺序,插入时间或访问键的频率。从这个意义上说:你应该考虑使用Map接口的其他类型的实现。

一种替代方法是使用TreeSet和客户比较器,根据这些时间限制自动排序

但请记住:计算机科学中只有两个难题:

  1. 命名
  2. 缓存失效

答案 2 :(得分:0)

每当您在地图中放置某些内容时,最简单的方法就是将String对象添加到列表中。然后你可以这样做:

while(map.size()>50000){
    map.remove(list.get(0))
    list.remove(0);
}

这是有效的,因为你实际上并不关心时间,只关心订单。

在这方面,队列会比列表更好,因为除了访问和删除第一个元素之外,您不需要任何其他内容

答案 3 :(得分:0)

我已经修改了Android框架中的LruCache类来做到这一点。

这是完整的代码。

import java.util.LinkedHashMap;
import java.util.Map;

public class RemoveOldHashMap<K, V> {
    private final LinkedHashMap<K, V> map;
    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;
    private int maxSize;
    private int putCount;
    private int createCount;
    private int evictionCount;
    private int hitCount;
    private int missCount;
    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    public RemoveOldHashMap(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, false); // false for "interaction order"
    }
    /**
     * Returns the value for {@code key} if it exists in the cache or can be
     * created by {@code #create}. If a value was returned, it is moved to the
     * head of the queue. This returns null if a value is not cached and cannot
     * be created.
     */
    public synchronized final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        for (K k : map.keySet()) {
            System.out.println("k = " + k);
        }

        V result = map.get(key);

        for (K k : map.keySet()) {
            System.out.println("k = " + k);
        }

        if (result != null) {
            hitCount++;
            return result;
        }
        missCount++;
        // TODO: release the lock while calling this potentially slow user code
        result = create(key);
        if (result != null) {
            createCount++;
            size += safeSizeOf(key, result);
            map.put(key, result);
            trimToSize(maxSize);
        }
        return result;
    }
    /**
     * Caches {@code value} for {@code key}. The value is moved to the head of
     * the queue.
     *
     * @return the previous value mapped by {@code key}. Although that entry is
     *     no longer cached, it has not been passed to {@link #entryEvicted}.
     */
    public synchronized final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }
        putCount++;
        size += safeSizeOf(key, value);
        V previous = map.put(key, value);
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
        trimToSize(maxSize);
        return previous;
    }
    private void trimToSize(int maxSize) {
        while (size > maxSize && !map.isEmpty()) {
            Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
            if (toEvict == null) {
                break; // map is empty; if size is not 0 then throw an error below
            }
            K key = toEvict.getKey();
            V value = toEvict.getValue();
            map.remove(key);
            size -= safeSizeOf(key, value);
            evictionCount++;
            // TODO: release the lock while calling this potentially slow user code
            entryEvicted(key, value);
        }
        if (size < 0 || (map.isEmpty() && size != 0)) {
            throw new IllegalStateException(getClass().getName()
                    + ".sizeOf() is reporting inconsistent results!");
        }
    }
    /**
     * Removes the entry for {@code key} if it exists.
     *
     * @return the previous value mapped by {@code key}. Although that entry is
     *     no longer cached, it has not been passed to {@link #entryEvicted}.
     */
    public synchronized final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V previous = map.remove(key);
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
        return previous;
    }
    /**
     * Called for entries that have reached the tail of the least recently used
     * queue and are be removed. The default implementation does nothing.
     */
    protected void entryEvicted(K key, V value) {}
    /**
     * Called after a cache miss to compute a value for the corresponding key.
     * Returns the computed value or null if no value can be computed. The
     * default implementation returns null.
     */
    protected V create(K key) {
        return null;
    }
    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }
    /**
     * Returns the size of the entry for {@code key} and {@code value} in
     * user-defined units.  The default implementation returns 1 so that size
     * is the number of entries and max size is the maximum number of entries.
     *
     * <p>An entry's size must not change while it is in the cache.
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }
    /**
     * Clear the cache, calling {@link #entryEvicted} on each removed entry.
     */
    public synchronized final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }
    /**
     * For caches that do not override {@link #sizeOf}, this returns the number
     * of entries in the cache. For all other caches, this returns the sum of
     * the sizes of the entries in this cache.
     */
    public synchronized final int size() {
        return size;
    }
    /**
     * For caches that do not override {@link #sizeOf}, this returns the maximum
     * number of entries in the cache. For all other caches, this returns the
     * maximum sum of the sizes of the entries in this cache.
     */
    public synchronized final int maxSize() {
        return maxSize;
    }
    /**
     * Returns the number of times {@link #get} returned a value.
     */
    public synchronized final int hitCount() {
        return hitCount;
    }
    /**
     * Returns the number of times {@link #get} returned null or required a new
     * value to be created.
     */
    public synchronized final int missCount() {
        return missCount;
    }
    /**
     * Returns the number of times {@link #create(Object)} returned a value.
     */
    public synchronized final int createCount() {
        return createCount;
    }
    /**
     * Returns the number of times {@link #put} was called.
     */
    public synchronized final int putCount() {
        return putCount;
    }
    /**
     * Returns the number of values that have been evicted.
     */
    public synchronized final int evictionCount() {
        return evictionCount;
    }
    /**
     * Returns a copy of the current contents of the cache, ordered from least
     * recently accessed to most recently accessed.
     */
    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }
    @Override public synchronized final String toString() {
        int accesses = hitCount + missCount;
        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                maxSize, hitCount, missCount, hitPercent);
    }
}

如何使用 在我的示例中,我将在Integer键中映射String对象。限制是2个对象,但您应该更改以实现目标。

RemoveOldHashMap<Integer, String> hash = new RemoveOldHashMap<Integer, String>(2 /* here is max value that the internal counter reaches */ ) {
    // Override to tell how your object is measured
    @Override
    protected int sizeOf(Integer key, String value) {
        return 1; // the size of your object
    }
};

参考: LruCache