HashMap
是开发中经常使用到的集合类,Java中还有一个类似的类叫 WeakHashMap
,本文来分析下 WeakHashMap
的实现原理。
原理
从key的存储上分析,在put操作时,如果不存在key值,则新建一个 Entry
:
public V put(K key, V value) {
Object k = maskNull(key);
int h = hash(k);
Entry<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);
// 如果有的话,则替换新
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
if (h == e.hash && eq(k, e.get())) {
V oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
// 创建新的key
modCount++;
Entry<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
if (++size >= threshold)
resize(tab.length * 2);
return null;
}
Entry
继承了 WeakReference
,这里就说明了put到 WeakHashMap
中的key是以 WeakReference
类型存储的:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
/**
* Creates new entry.
*/
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
// ...
}
WeakReference
有两个构造方法,单独的key或者key和queue,两者的区别在于key在要引用的时候,后者将其加入到队列中:
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
对于 WeakHashMap的key
清理,可以根据上文的queue进行处理,因为queue中的key都是没有引用的对象了(除queue引用), WeakHashMap
清理函数为 expungeStaleEntries
,查看调用的地方:
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}
public int size() {
if (size == 0)
return 0;
expungeStaleEntries();
return size;
}
void resize(int newCapacity) {
// ...
expungeStaleEntries();
transfer(newTable, oldTable);
table = oldTable;
}
在Map的基本操作get、put、remove等,都会触发上述方法的调用,进而达到清理失效key的效果;
使用场景
很多技术同学给出了Tomcat中的一个多级缓存使用的场景:
其中二级缓存使用了 WeakHashMap
,整体上是一个LRUCache,一级缓存保持固定的容量,超过最大容量后,将其移入二级缓存中,查找元素时,先从一级缓存中查找,不存在的时候,从二级缓存中查找,如果存在,则同时将其移入一级缓存中,实现LRU缓存,代码如下:
public final class ConcurrentCache<K,V> {
private final int size;
private final Map<K,V> eden;
private final Map<K,V> longterm;
public ConcurrentCache(int size) {
this.size = size;
this.eden = new ConcurrentHashMap<>(size);
this.longterm = new WeakHashMap<>(size);
}
public V get(K k) {
V v = this.eden.get(k);
if (v == null) {
synchronized (longterm) {
v = this.longterm.get(k);
}
if (v != null) {
this.eden.put(k, v);
}
}
return v;
}
public void put(K k, V v) {
if (this.eden.size() >= size) {
synchronized (longterm) {
this.longterm.putAll(this.eden);
}
this.eden.clear();
}
this.eden.put(k, v);
}
}
Android同学经常会遇到持有相关对象,造成内存泄漏的问题,例如常用的全局listener,可以使用 WeakHashMap
便捷的解决问题,但是不完全能避免内存泄漏,因为其清理等待需要下一次操作之后,可能会有一个元素不能释放,可以使用 WeakReference
和 HashMap
组合解决,构造 WeakReference
时不传入队列,在元素不使用后,直接由垃圾回收器回收。
小结
本文介绍了 WeakHashMap
的原理,对于需要频繁操作且内存有限的场景,可以尝试下。
如有任何知识产权、版权问题或理论错误,还请指正。
转载请注明原作者及以上信息。