在JDK 1.8源码中,WeakHashMap有如下类注释。
Hash table based implementation of the Map interface, with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use.
基于哈希表的Map接口实现,带有弱引用的键。当WeakHashMap中的键不再被使用时,该项将被自动删除。
本文将结合源码,带你了解它的底层实现。
1 数据结构
// 默认的初始容量
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// 默认负载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 哈希表,必要时扩容,长度增长1倍
Entry<K,V>[] table;
// 阈值,capacity * load factor
private int threshold;
// 负载因子
private final float loadFactor;
// 被gc回收的弱引用key,对应的entry将放入queue中
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
内部类Entry,是WeakReference的子类,将键包装成弱引用。
通过next属性,在哈希冲突时形成单链表。
2 主要方法实现
2.1 构建实例
使用默认值创建,也可指定initialCapacity、loadFactor。
也可基于一个Map实例来创建。
2.2 put方法
- WeakHashMap中允许key和value为null,
key == null时被将使用常量Object NULL_KEY; - 当存在哈希冲突时,将冲突项构建成链表,put新key时插入到链表头;
- put一个新key后,可能触发扩容;
- 每次扩容时,哈希表length增加1倍。
2.3 get方法
实现很简单。
2.4 删除失效的键
- 当键不再被使用时(没有任何强引用),将随时被垃圾回收;
- 键被gc回收后,它的entry会被放入
ReferenceQueue<Object> queue中; expungeStaleEntries方法中,循环调用queue.poll()拿到失效的entry,并将它从哈希表中移除;- WeakHashMap的get、put、remove、containsValue、forEach、resize等方法,都会触发清理失效的entry。
WeakHashMap自动清理失效的项,依赖于WeakReference、ReferenceQueue。
2.5 remove方法
主要逻辑与expungeStaleEntries()相似,只是
- remove()由用户程序主动调用,并指定删除的key;
- expungeStaleEntries()只能在调用WeakHashMap的某些方法时,被动触发。
3 总结
3.1 WeakHashMap的特点
- 能自动释放不再使用的key所占内存,提高应用程序性能;
- 由于需要频繁移除失效的键,在性能上会比HashMap略差;
- 当哈希冲突严重时,单链表下的get(),性能不及HashMap中的红黑树;
- 它是线程不安全的,应防止并发修改;
- 在一些场景下,使用它来避免发生内存泄漏。
3.2 应用场景
WeakHashMap通常用于需要动态管理大量对象的应用中,可以避免内存泄漏问题:
- 缓存系统:使用WeakHashMap作为缓存的实现,entry项会在key不再被强引用时自动移除,避免浪费内存;
- 生命周期管理:如数据库连接、HTTP连接池、线程池等,使用WeakHashMap来保存对象,避免忘记关闭资源而造成的内存泄漏。