ConcurrentHashMap第五讲:get方法,remove方法

本文详细解析了HashMap中的get方法实现原理,包括扰动运算、链表与红黑树查询流程;同时深入探讨了remove方法的具体实现,涵盖替换与删除操作及不同场景下的处理方式。

get方法

前面几章我们讲解了,put的源码以及里面的扩容机制,现在我们来讲解get方法,代码相对比较容易理解:

   public V get(Object key) {
        // tab 引用 map.table
        // e 当前元素
        // p 目标节点
        // n table数组长度
        // eh 当前元素hash
        // ek 当前元素key
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;

        // 扰动运算后得到  更散列的hash值
        int h = spread(key.hashCode());

        // 条件一:(tab = table) != null
        //  true-> 表示已经put过数据    false->表示创建完map后,并没有put
        // 条件二:(n = tab.length) > 0
        // 条件三:(e = tabAt(tab, (n - 1) & h)) != null
        //          true-> 当前key 寻址的桶位 有值
        //          false->当前key 寻址的桶位是null,直接返回
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            //前置条件:当前有数据

            //对比头节点hash与查询key的hash是否一致
            // 条件成立:什么头节点与查询key完全一致
            if ((eh = e.hash) == h) {
                //完全比对 key
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            // 条件成立:
            // -1  fwd 说明当前table正在扩容,且当前查询的这个桶位的数据 已经被迁移走了
            // -2  TreeBin节点,需要使用TreeBin 提供的find 方法查询
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;

            // 当前桶位已经形成链表的这种情况
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

FWD节点 ForwardingNode类中的find方法,(重写了Node的find):

        Node<K,V> find(int h, Object k) {
            // loop to avoid arbitrarily deep recursion on forwarding nodes
            outer: for (Node<K,V>[] tab = nextTable;;) {
                // n 表示为扩容创建的 新表的长度
                // e 表示在扩容而创建新表使用 寻址算法 得到的 桶位头节点
                Node<K,V> e; int n;
                // 条件一:永远不成立
                // 条件二:永远不成立
                // 条件三:永远不成立
                // 条件四:在新扩容的表中 重新定位hash对应的头节点
                //      true-> 1.在oldTable中 对应的桶位在迁移之前就是null
                //              2.扩容完成后,有其它写线程,将此桶位设置为了null
                if (k == null || tab == null || (n = tab.length) == 0 ||
                    (e = tabAt(tab, (n - 1) & h)) == null)
                    return null;

                //前置条件:扩容后的表 对应hash的桶位一定不是null,e为此桶位的头节点
                // e可能为哪些node类型?
                // 1.node类型
                // 2.TreeBin类型
                // 3.FWD类型
                for (;;) {
                    // eh 新扩容后指定桶位的当前节点的hash
                    // ek 新扩容后指定桶位的当前节点的key
                    int eh; K ek;
                    // 条件成立:说明新扩容后的表,当前命中的桶位就是要查询的数据
                    if ((eh = e.hash) == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                    // eh < 0
                    // -1  fwd      -2  TreeBin节点
                    // 两种情况:1.TreeBin类型, 2.FWD类型 (新扩容的表,在并发的情况下,可能在此方法  再次拿到FWD类型)
                    if (eh < 0) {
                        if (e instanceof ForwardingNode) {
                            tab = ((ForwardingNode<K,V>)e).nextTable;
                            continue outer;
                        }
                        else
                            // 说明桶位为TreeBin节点,红黑树的查询
                            return e.find(h, k);
                    }
                    // 当前桶位的头节点没有命中,一定是链表
                    // 1.将当前元素指向链表的下一个元素
                    // 2.判断是否为空,如果为null,返回null
                    if ((e = e.next) == null)
                        return null;
                }
            }
        }

remove 方法

remove 本质上是一种替换为null 的操作,所以里面使用的方法是replaceNode

remove方法中,调用replaceNode方法,传入的第一个null 代表要把key对应的值替换为value值,第二个null,代表key对应值的期望值(如果为空就不判断,不为空,就需要比对value值)

    public V remove(Object key) {
        return replaceNode(key, null, null);
    }
    final V replaceNode(Object key, V value, Object cv) {
        // 计算 key 经过扰动运算后的hash
        int hash = spread(key.hashCode());
        // 自旋
        for (Node<K,V>[] tab = table;;) {
            // f 代表桶位头节点
            // n 表示当前table 数组长度
            // i 表示hash命中桶位下标
            // fh 表示桶位头节点 hash
            Node<K,V> f; int n, i, fh;

            //CASE1:
            // 条件一:tab == null  true-> 表示当前map.table未初始化, false-> 已经初始化
            // 条件二:(n = tab.length) == 0    true-> 表示当前map.table未初始化, false-> 已经初始化
            // 条件三: (f = tabAt(tab, i = (n - 1) & hash)) == null true-> 命中中桶位为null,break退出
            if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
                break;

            //CASE2:
            // 前置条件:当前桶位不为null
            //  true->FWD节点, 说明当前table正在扩容中,当前是个写操作,所以需要协助table完成扩容,
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);

            //CASE3:
            // 前置条件:当前桶位不为null
            // 说明当前桶位可能是链表 也可能是 红黑树TreeBin
            else {
                // oldVal: 保留替换之前的数据引用
                V oldVal = null;
                // validated: 校验标记
                boolean validated = false;

                // 加锁当前桶位 头节点
                synchronized (f) {

                    // 判断 锁的对象对不对,防止其他线程在加锁前修改了数据
                    if (tabAt(tab, i) == f) {

                        // 条件成立:说明桶位 为链表 或者单个node
                        if (fh >= 0) {
                            validated = true;
                            //e 表示当前循环处理元素
                            //pred 表示当前循环节点的上一个节点
                            for (Node<K,V> e = f, pred = null;;) {
                                // 当前节点key
                                K ek;
                                // 条件一:e.hash == hash true->说明当前节点的hash与查找节点hash一致
                                // 条件二:(ek = e.key) == key || (ek != null && key.equals(ek))
                                //        true-> 说明key 与查询的key完全一致
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    // 当前节点的value
                                    V ev = e.val;

                                    // 条件一:cv == null true-> 替换的值为null 那么就是一个删除操作
                                    // 条件二:cv == ev || (ev != null && cv.equals(ev))  那么是一个替换操作
                                    if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                        // 删除 或 替换

                                        // 将当前节点的值 赋给 oldVal 后续返回用到
                                        oldVal = ev;

                                        //条件成立:说明当前是一个替换操作
                                        // value 是传进来的替换值,如果没有替换值就是删除操作
                                        if (value != null)
                                            // 直接替换
                                            e.val = value;

                                        // 条件成立:删除操作  并且删除的不是头节点
                                        else if (pred != null)
                                            // 当前节点的上一个节点,指向当前节点的下一个节点
                                            pred.next = e.next;

                                        // 条件成立:删除操作  删除的是头节点
                                        else
                                            // 说明当前节点即为头节点,只需要将 桶位位置设置为头节点的下一个节点
                                            setTabAt(tab, i, e.next);
                                    }
                                    break;
                                }
                                pred = e;
                                if ((e = e.next) == null)
                                    break;
                            }
                        }

                        // 条件成立:TreeBin
                        else if (f instanceof TreeBin) {
                            validated = true;

                            // 转换为实际类型 TreeBin t
                            TreeBin<K,V> t = (TreeBin<K,V>)f;

                            //r 表示 红黑树 根节点
                            //p 表示 红黑树中查找到对应key 一致的node
                            TreeNode<K,V> r, p;

                            // 条件一:(r = t.root) != null   理论上是成立
                            // 条件二:TreeNode.findTreeNode 以当前节点为入口,向下查找key(包括本身节点)
                            //          true->模式查找到相应key 对应的node节点。会赋值给p
                            if ((r = t.root) != null &&
                                (p = r.findTreeNode(hash, key, null)) != null) {
                                //保存 p.val 到 pv
                                V pv = p.val;

                                // 条件一:cv == null  成立:删除操作
                                // 条件二:cv == pv || (pv != null && cv.equals(pv))  成立:替换操作
                                if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                    // 删除 或 替换
                                    oldVal = pv;

                                    // 替换操作
                                    if (value != null)
                                        p.val = value;

                                    //删除操作
                                    else if (t.removeTreeNode(p))
                                        // untreeify:把红黑树 转换为链表
                                        setTabAt(tab, i, untreeify(t.first));
                                }
                            }
                        }
                    }
                }
                //当其它线程修改过桶位 头节点时,当前线程sync头节点 锁错对象时,validated 为false,会进入下次自旋
                //说明操作完了,不需要自旋了,需要break 或者 return
                if (validated) {

                    // 条件成立:找到了 删除或替换 的key-value 了
                    if (oldVal != null) {
                        // value == null当前是删除操作
                        if (value == null)
                            //计数统计,传-1 是让addCount 方法中不扩容
                            addCount(-1L, -1);
                        return oldVal;
                    }
                    break;
                }
            }
        }
        return null;
    }
【重要提醒】:本资源原始文件(TIF)压缩包大小超过5G(解压后达到8G),远超平台上传大小限制(1000M),因而将原始DEM文件放置于百度网盘中,大家可自行下载后并加载使用。在本资源mxd文件中展现了博主加载原始DEM文件后呈现的全球高程状况,可通过word预览。大家下载后需要将TIF文件再一次加载进mxd文件中才会正常显示,可根据自身喜好自行配色。如有疑问,下载前请联系博主。本产品提供覆盖全球的30米空间分辨率数字高程模型(DEM)栅格数据(GeoTIFF 格式),并附带标准化的全球国家/地区行政区划矢量(Shapefile)及配套的可编辑 MXD 工程文件与标准成图 TIF 输出。该套成果结合高程基础数据与政区边界信息,既可作为地形分析的高质量底层数据,也可直接用于专题制图与跨尺度空间分析。数据来源与构建DEM 通过融合多源高程产品(如公开的卫星雷达/光学高程数据与地形测绘成果)进行镶嵌、无缝拼接和误差校正以保证空间连续性与精度一致性。构建流程包括影像配准、投影统一、空洞填补、低频噪声去除、边坡滤波与水文一致性校核(以保证流向连通性)。行政区划矢量依据权威国家/国际边界数据进行标准化处理,包含国家/地区名称、ISO 编码、面积和几何属性等字段。本资源可用于地形与地貌研究、流域水文与洪水风险模拟、基础工程选址、生态与土地利用分析、可视化三维展示、交通与管线规划及灾害评估等。行政区矢量便于行政统计、政策分析与专题地图叠加展示。在大范围分析中建议先裁剪感兴趣区以提高处理效率;工程级应用请结合更高精度的地方测绘或 LiDAR 数据做最终决策依据。博主使用的MXD 基于 ArcMap 10.8 工程格式;QGIS 用户可直接加载 GeoTIFF 与 Shapefile。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值