WeakHashMap的原理

WeakHashMap的原理简介WeakHashMap和HashMap一样,WeakHashMap也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以为null。不过WeakHashMap的键是“弱键”(注:源码中Entry中的定义是这样的:privatestaticclassEntry<K,V>extendsWeakReference implementsMap.Ent…

大家好,又见面了,我是你们的朋友全栈君。

简介

WeakHashMap和HashMap一样,WeakHashMap也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以为null。不过WeakHashMap的键是“弱键”(注:源码中Entry中的定义是这样的:private static class Entry<K,V> extends WeakReference implements Map.Entry<K,V>,即Entry实现了WeakReference类),当WeakHashMap某个键不再正常使用时,会被从WeakHashMap自动删除。更精确的说,对于一个给定的键,其映射的存在并不能阻止垃圾回收器对该键的丢弃,这就使该键称为被终止的,被终止,然后被回收,这样,这就可以认为该键值对应该被WeakHashMap删除。因此,WeakHashMap使用了弱引用作为内部数据的存储方案,,WeakHashMap可以作为简单缓存表的解决方案,当系统内存不足时,垃圾收集器会自动的清除没有在任何其他地方被引用的键值对。如果需要用一张很大的Map作为缓存表时,那么可以考虑使用WeakHashMap。

在WeakHashMap实现中,借用了ReferenceQueue这个“监听器”来保存被GC回收的”弱键”,然后在每次使用WeakHashMap时,就在WeakHashMap中删除ReferenceQueue中保存的键值对。即WeakHashMap的实现是通过借用ReferenceQueue这个“监听器”来优雅的实现自动删除那些引用不可达的key的。这个我在上一篇的博客中已经做了说明。Java引用Reference学习

WeakHashMap是通过数组table保存Entry(键值对);每个Entry实际上就是一个链表来实现的。当某“弱键”不再被其它对象引用,就会被GC回收时,这个“弱键”也同时被添加到ReferenceQueue队列中。当下一步我们需要操作WeakHashMap时,会先同步table、queue,table中保存了全部的键值对,而queue中保存的是GC回收的键值对;同步他们,就是删除table中被GC回收的键值对。

我们可以在WeakHashMap的源码中看到这样的一个属性:

/**
* 申明的WeakEntries的queue指针
*/
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

我们来看一下内部Entry类的定义,你就会明白很多事情了:

/*
 * 原来将Entry定义为WeakReference,这样就会自动消失。
 */
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    int hash;
    Entry<K,V> next;

    /**
    * 创建一个新的Entry
    */
    Entry(Object key, V value,
            ReferenceQueue<Object> queue,
            int hash, Entry<K,V> next) {
        // 传入ReferenceQueue和hash,这个可能是在已有头Entry的基础上使用
        super(key, queue);
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }
    ......
}


如何创建Entry

在进行put操作的时候,会进行Key的建立。大家注意这个弱引用不是指我们put的key是弱引用,而是指内部定义的Entry是弱引用,不要忘记这一点。

public V put(K key, V value) {
    // null key的处理
    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;
        }
    }

    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;
}


移除失效元素

进一步研读代码我们可以发现,size方法,getTable等一些的操作中,都会调用一个叫expungeStaleEntries的方法,这个方法的内容如下:

/**
 * 从桶中移除失效的key-value(Entry)
 */
private void expungeStaleEntries() {
    // 从Queue中取被回收的数据,然后进行删除操作
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
                Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, table.length);

            // 进行删除的操作
            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    // Must not null out e.next;
                    // stale entries may be in use by a HashIterator
                    e.value = null; // Help GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

其实就是从Queue中取得,被垃圾回收机制回收的Entry,然后从Map中删除这个Entry,这是一种懒删除的方式,我们之前已经学习了Java的Reference机制,这里就不多研究了。

总结

我觉得这种数据结构,可能面临丢失数据的风险,所以使用场景是哪些不怕丢失数据的地方。我想了一下,什么数据不怕丢,最容易想到的就是可以恢复的数据;然后我想到了内存缓存,的确缓存数据最不怕丢失,因为缓存数据往往都是可以想办法恢复的。所以当我们缓存的数据比较多的时候,使用这个数据结构的确可以帮助我们在系统内存紧张的时候,放弃缓存,然后重新缓存。但是Weak的特性是每次gc都极可能丢失,也会带来不少的问题。总之呢,这种开发的思想真的很好。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/138461.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • c语言rtp协议,RTP系列:RTP协议详解和分析

    c语言rtp协议,RTP系列:RTP协议详解和分析1、RTP概述实时传输协议(Real-timeTransportProtocol或简写RTP)是一个网络传输协议,作为因特网标准在RFC3550(该文档的旧版本是RFC1889)有详细说明。RFC3551(STD65,旧版本是RFC1890)详细描述了使用最小控制的音频和视频会议。RTP协议详细说明了在互联网上传递音频和视频的标准数据包格式。它一开始被设计为一个多播协议,但后来被用在…

    2022年6月28日
    71
  • 大数据平台建设方案(项目需求与技术方案)[通俗易懂]

    大数据平台建设方案(项目需求与技术方案)[通俗易懂]一、项目背景 “十三五”期间,随着我国现代信息技术的蓬勃发展,信息化建设模式发生根本性转变,一场以云计算、大数据、物联网、移动应用等技术为核心的“新IT”浪潮风起云涌,信息化应用进入一个“新常态”。***(某政府部门)为积极应对“互联网+”和大数据时代的机遇和挑战,适应全省经济社会发展与改革要求,大数据平台应运而生。在这里我还是要推荐下我自己建的大数据学习交流qq裙: 957205…

    2022年4月30日
    45
  • 净推荐值(NPS):用户忠诚度测量的基本原理及方法

    净推荐值(NPS):用户忠诚度测量的基本原理及方法文章分享了一个衡量用户与产品或服务之间关系的指标:NPS,干货满满,希望对你有益。初识NPS作为互联网行业的用户体验从业者,我们都或多或少会接触一些衡量用户与产品或服务之间关系的指标,常见的指标如活跃度、留存率、用户满意度等。近几年,NPS(NetPromoterScore净推荐值)在国内流行起来,越来越多的行业及企业开始使用NPS指标作为衡量用户口碑的工具,如通信服务行业的中国移…

    2022年6月14日
    100
  • echart旭日图_基于Echarts4.0实现旭日图[通俗易懂]

    echart旭日图_基于Echarts4.0实现旭日图[通俗易懂]昨天Echarts4.0正式发布,随着4.0而来的是一系列的更新,挑几个主要的简单说明:1.展示方面通过增量渲染技术(4.0+)ECharts能够展现千万级的数据量2.针对移动端优化,移动端小屏上适于用手指在坐标系中进行缩放、平移。可选的SVG渲染模块让图表在移动端更加节省内存。3.增加多种渲染方案,可实现跨平台使用,现有三种方案,可渲染Canvas、SVG(4.0+)、VML的形式渲染图…

    2022年9月26日
    0
  • MATLAB中导入数据:importdata函数

    MATLAB中导入数据:importdata函数

    2021年12月6日
    61
  • 各大公司的大数据质量监控平台

    各大公司的大数据质量监控平台转自:https://zhuanlan.zhihu.com/p/41679658在这个信息化时代,你用手机打开微信聊天、打开京东app浏览商品、访问百度搜索、甚至某些app给你推送的信息流等等,数据无时无刻不在产生。数据,已经成为互联网企业非常依赖的新型重要资产。数据质量的好坏直接关系到信息的精准度,也影响到企业的生存和竞争力。MichaelHammer(《Reengineeringt…

    2022年5月1日
    196

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号