我在Java中有一个哈希映射,我需要限制其大小(50000的顺序)。但我应该只删除最旧的项目。项目的时间戳存储在条目对象的字段中:
Map<String, MyModel> snapshot = new HashMap<>();
和
public class MyModel {
private ZonedDateTime createdAt;
// other fields...
}
我也按时间戳顺序将它们插入到地图中。
完成这种删除最旧条目的最有效方法是什么?请注意,时间“阈值”未知,只有所需的最终地图大小。
答案 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和客户比较器,根据这些时间限制自动排序。
但请记住:计算机科学中只有两个难题:
答案 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