弄懂WeakHashMap实现原理,早日面试成为Java技术专家

跟HashMap一样,WeakHashMap也是一个哈希表。它比HashMap多出来的功能就是,存储在WeakHashMap中且没有被其他地方引用的key可以被GC回收,而如果把key保存在HashMap中,则无论这个key在其他地方有没有被引用,这个key一直驻留在JVM内存,从而产生泄漏的风险。

我们知道,对于弱引用WeakReference而言,只要发生一次GC,它内部引用的对象就会立即被GC回收。所以说WeakHashMap的key是一个weak key,把key和value放入WeakHashMap之后,如果这个key没有被其他地方引用,这对key-value以及用来保存这对key-value的entry都会被回收。而value是否被其他地方引用,不影响WeakHashMap中这个key是否被GC。

本文以图解方式分析WeakHashMap的核心实现逻辑,助你早日成为Java技术专家。

1. WeakHashMap实现总览

我们知道WeakHashMap是一个java集合工具类,主要起数据结构的作用,用于在内存中存取数据。所以,从宏观角度看WeakHashMap,它由两部分组成:

 其实,Java Collection Framework中的所有集合工具类都可以这么理解,即包含2部分:数据+操作。

那么,它的数据又是由哪些东西组成呢,如图所示。

 WeakHashMap内部只包含6项数据,即entry数组,元素个数size,扩容阈值threshold,loadFactor装载因子,queue引用队列,以及modCount结构变更次数。把这6个数据放在一起,看作一个整体,这个整体就是一个WeakHashMap。

其中,引用队列queue的作用就是,在key被GC以后,让WeakHashMap能及时得到通知,从而把这些key对应的entry释放。

另外,modCount其实只起到一个优化作用。它主要是用来保证在,并发场景下尽可能检测到错误而已。严格说来,它算不上WeakHashMap自身的数据。

除数据字段外,WeakHashMap代码其实还包含一些常量定义。但是个人认为,常量定义只是为了让代码的可读性提高,让一些数值比如16,在它们出现的地方,让我们立即明白此处参数的含义,它们并不属于WeakHashMap。

2. 经典的哈希表

在上面6个成员字段中,有两个比较重要,一个是table[]数组,另一个是引用队列queue。引用队列我们后面再分析。而这个 table[] 就是"数据结构"这门计算机专业基础课中学过的拉链表结构,如图所示。

 这与JDK 7中HashMap实现是一致的,但是到JDK 8,HashMap的结构已经变了,WeakHashMap还是从前那个少年,没有一丝丝改变。

关于这个拉链表,常见的面试考点如下:

1) 长度为什么必须是2的n次幂?

答:因为2的n次幂这种数非常特殊,它的二进制形式中只有一个1,当减去1的时候,即2^n – 1,原来的1的位置变为0,后面的所有0都变为1,这种左边全是0、右边全是1的数字非常适合作为掩码,如下图所示。

 把这个掩码与任意一个数进行比特与运算,所得到的数字一定是小于2^n的,因此用(2^n-1)可以最快速度得到entry数组的入口下标。

2)在拉链表中,新增节点是插入在链表头部还是尾部?

答:如果不考虑性能问题,那么插入在头部和插入在尾部其实是一样的。但是,HashMap必须要考虑性能,那么显然,对于单链表而言,在表头插入的速度是最快的。而且,根据时间局部性原理,刚存入的数据,在接下来最近的一段时间内,再次被读取的几率很大,所以也要求我们在链表头部插入。

3. WeakHashMap实现逻辑总览

弄清楚WeakHashMap内部的数据成员之后,接下来就是研究两大组成部分的另外一部分——代码实现。虽然从表面上看,WeakHashMap的代码挺多的,但是因为WeakHashMap是一个静态Java工具类,所以它的各个公共方法的实现互相独立,相互之间的关联和依赖很小,所以可以分别单独学习,这就降低了学习难度。

从宏观角度看,WeakHashMap的实现代码可分为9个部分,如下图所示。

 可以看出,WeakHashMap的实现代码包括:constructors (构造方法)、internal utilities (一些简短的辅助工具)、hash utils (对于HashMap内部实现有特定作用的小工具,例如hash、indexFor之类的方法)、Map<K, V> API (对于Map接口中方法的实现)、maintainers (起哈希表维护作用的方法,例如resize方法, transfer方法等)、class Entry(内部定义的类,用于存放一对key、value)、iterators (迭代器类)、views (视图类,主要是EntrySet, KeySet, Values,主体数据的镜像)、spliterators (分裂迭代器,用于Java 8 Stream API)。

以上内容都是其他文章不可能讲到的干货哦,建议收藏。下面这张图比上面这张图更加详细一点。

 

4. WeakHashMap内部各个API的实现细节

理解了数据结构、主要实现代码分9部分,你对WeakHashMap的理解差不多了。再想深入钻研WeakHashMap的话,就是代码实现细节了,比如get怎么根据key找到插入位置、插入元素触发扩容的具体操作等等。限于篇幅,这一部分留到下一篇讨论。

5. WeakHashMap中的weak key是怎么实现的

我们知道WeakHashMap的特色功能就在于它的weak key,那么究竟是怎么实现的呢?其实很简单,让Entry继承WeakReference,在存key的时候,把key放入WeakReference的main ref字段。放入Weak Reference字段保证key可以GC回收。

那么这个Entry对象本身,它自身是被table数组强引用的,它是怎么被GC回收的呢。答案就是通过ReferenceQueue。在创建WeakReference的时候,可以给它指定一个ReferenceQueue,作用就是在这个WeakReference里面的main ref所引用的对象被回收的时候,这个WeakRererence对象自身会进入ReferenceQueue。我们只要查看一下ReferenceQueue中有哪些对象,就知道哪些entry被回收了。

有了WeakReference和ReferenceQueue之后,只要再在get()之类的Map操作中,插入一些维护WeakHashMap的代码,例如清理stale元素的代码,就能保证WeakHashMap中无效数据被彻底清理掉了。在缓存读写操作中,插入一些维护性的代码,是很常见的设计。

至此,WeakHashMap最主要的代码实现就分析完了,想更加精确地钻研WeakHashMap,请关注本号,并且等待下一篇!

希望以上分析对您有帮助。如果对互联网编程、前后端、客户端、架构、分布式、高可用、高并发、高实时、电商、Redis、MySQL、Zookeeper、Spring、Android、浏览器插件、Java、Java虚拟机、Java Script、C/C++、Linux、个性化推荐、社区发现、机器学习、深度学习、强化学习、数据挖掘、论文、Gnuplot、LaTeX等感兴趣的话,欢迎关注本号。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值