WeakHashMap和HashMap相似,也是哈希表的实现,以键值对的形式存储数据,key和value都可以为null。不同的是WeakHashMap的键为“弱键”。

定义

public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
  • WeakHashMap<K,V>:HashMap是以key-value形式存储数据的
  • extends AbstractMap<K,V>:继承了AbstractMap,大大减少了实现Map接口的工作量。
  • implements Map<K,V>:实现了Map。AbstractMap已经继承了Map接口,为什么WeakHashMap还要实现Map接口呢?仔细看过Java容器其他源码的朋友会发现,不仅仅WeakHashMap这样做,其他实现类也经常这样做。网上的一些看法是这样做可以直观地表达出WeakHashMap实现了Map。

与HashMap相比,我们发现WeakHashMap并没有实现Cloneable和Serializable接口。

构造方法

WeakHashMap有四个构造方法:

  1. 用指定的初始化容量initial capacity 和负载因子load factor构造一个空WeakHashMap。
  2. 使用指定的初始化容量initial capacity和默认负载因子DEFAULT_LOAD_FACTOR(0.75)构造一个空WeakHashMap。
  3. 使用指定的初始化容量(16)和默认负载因子DEFAULT_LOAD_FACTOR(0.75)构造一个空WeakHashMap。
  4. 使用指定map构造新的WeakHashMap。使用指定的初始化容量(16)和默认负载因子DEFAULT_LOAD_FACTOR(0.75)。

核心方法

size()
从哈希表中删除被回收的key的映射后返回新的哈希表的大小

isEmpty()
判断哈希表大小是否为0

get( Object key)
返回指定的key对应的value,如果value为null,则返回null

containsKey( Object key)
如果map中含有key为指定参数key的键值对,返回true

put( K key, V value)
将指定参数key和指定参数value插入map中,如果key已经存在,那就替换key对应的value

resize( int newCapacity)
扩容,并将旧的map中的键值对插入到新的table中。当map大小超过threshold时,方法会自动调用。

putAll(Map<? extends K, ? extends V> m)
复制参数m中所有的键值对到weakHashMap中

remove( Object key)
删除weakHashMap中key为参数key的键值对

clear()
删除weakHashMap中所有的键值对

containsValue( Object value)
如果weakHashMap中的键值对有一对或多对的value为参数value,返回true

containsNullValue()
如果weakHashMap中的键值对有一对或多对的value为null,返回true

“弱键”

  1. 什么是“弱键”?
    先了解下什么是“弱引用”
    弱引用, 在进行垃圾回收时,无论当前内存是否足够,都会回收掉只被弱引用关联着的对象,因此其生命周期只存在于一个垃圾回收周期内。
    WeakHashMap的键就是弱引用。当某个键不再正常使用时,便自动移除其条目。

  2. “弱键”会对WeakHashMap产生什么影响?
    当一个键不再正常使用,键对应的键值对将自动从WeakHashMap中删除。键对应的键值对的存在并不阻止key被垃圾回收期回收,这就使该键称为可被终止的,最终被终止,被回收。当某个键被回收,它对应的键值对也就被从map中有效地删除了。所以WeakHashMap类表现地有些和其他的Map接口实现不同。

  3. “弱键”是如何实现的?
    WeakHashMap 中的Entry对象继承了 WeakReference,它把key封装成一个弱引用对象。

源码分析

存储结构
WeakHashMap 的 Entry 继承自 WeakReference,被 WeakReference 关联的对象在下一次垃圾回收时会被回收。
WeakHashMap 主要用来实现缓存,通过使用 WeakHashMap 来引用缓存对象,由 JVM 对这部分缓存进行回收。

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>

ConcurrentCache
Tomcat 中的 ConcurrentCache 使用了 WeakHashMap 来实现缓存功能。
ConcurrentCache 采取的是分代缓存:

  • 经常使用的对象放入 eden 中,eden 使用 ConcurrentHashMap 实现,不用担心会被回收(伊甸园);
  • 不常用的对象放入 longterm,longterm 使用 WeakHashMap 实现,这些老对象会被垃圾收集器回收。
  • 当调用 get() 方法时,会先从 eden 区获取,如果没有找到的话再到 longterm 获取,当从 longterm 获取到就把对象放入 eden 中,从而保证经常被访问的节点不容易被回收。
  • 当调用 put() 方法时,如果 eden 的大小超过了 size,那么就将 eden 中的所有对象都放入 longterm 中,利用虚拟机回收掉一部分不经常使用的对象。
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) {
            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) {
            this.longterm.putAll(this.eden);
            this.eden.clear();
        }
        this.eden.put(k, v);
    }
}

WeakHashMap与HashMap比较

不同点

不同点 HashMap WeakHashMap
数据结构 数组+链表+红黑树 数组+链表+队列
强引用 弱引用
是否实现Cloneable和Serializabl

相同点

  • 都是基于哈希表的实现。
  • 都以键值对的形式存储数据。
  • 都继承了AbstractMap,实现了Map接口。
  • 都支持key和value为null。
  • 都是非同步的。
  • 都是无序的。