HashMap的使用:put、remove和get方法原理

news2024/12/22 20:03:11

关联项目需求

进行FeatureAB上报的时候,我们使用HashSet的add方法存key值,如果key已存在,则add失败,返回false,如果key不存在,add成功,返回true。

看源码中HashSet的add(E e)方法实现:

HashSet#add

底层还是使用的hashMap的put(E e,Object o)方法。

HashMap的结构:

使用数组的结构存链表(或树)的节点

transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
        ...
    }

hashMap结构:

HashMap的put方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

hashMap#put

实际上调用的是putVal方法:

储备知识点:

  1. 移位运算:左移 1 << 4 == 1*24 数组的初始容量为16

  1. String内容一样,他们的hashCode也一样

  1. 按位与运算:00 1111 1111 & 11 0000 0000 和 00 1111 1111 & 10 0000 0000 结果一样都为0

  1. 哈希碰撞原理:上面的例子里11 0000 0000和10 0000 0000 代表的hash值不同,但是都放入同一个下标的数组里

定义的一些字段所表示的含义:

  1. table是一组头结点数组

  1. n赋值为数组长度

  1. i = (n - 1) & hash按位与求出该key所在头结点数组的位置(哈希碰撞)

  1. p为找到的头结点

  1. e为最终找到的key对应的节点

  1. TREEIFY_THRESHOLD为8

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i; 
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;       //n为数组长度
        if ((p = tab[i = (n - 1) & hash]) == null)  //(n - 1) & hash按位与求出该key所在头结点数组的位置
            tab[i] = newNode(hash, key, value, null); //没有找到时,链表新创建一个节点
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;   //头结点就是对应key的节点,p为头节点
            else if (p instanceof TreeNode)  //p如果是个二叉树节点
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  //链表长度达到一定长度时会转为树结构
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {  // 找到下一个节点,下一个节点为空则新建一个节点插入(hashCode一样,key不一样)
                        p.next = newNode(hash, key, value, null); //新建一个节点
                        if (binCount >= TREEIFY_THRESHOLD - 1) // 节点数大于8转为树结构
                            //这里binCount从0开始,binCount = 7时,节点数为9
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key,如果有存在的节点
                V oldValue = e.value;  
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;  //给节点的value赋值
                afterNodeAccess(e);
                return oldValue;  //返回原有的value
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

HashMap#putVal

hashMap结构:

put方法总结:(结合代码和图)

  1. 如果key所在数组位置头结点没有值,则新加一个头结点,返回上一个节点null

  1. 如果key所在的数组位置头结点有值,且头结点key和要插入的key一致则将头结点取出,将value值改为新的value,返回上一个节点

  1. 如果key所在的数组位置头结点有值,且头结点key和要插入的key不一致,则查找链表或树里key对应的节点,取出节点将value值改为新的value,返回上一个节点

HashMap的remove方法

remove方法实际是调用的removeNode方法

public V remove(Object key) {
    Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
    null : e.value;
}

HashMap#remove

一些字段所表示的含义:

  1. index 为要删除的节点在数组下标的位置

  1. node为要删除的节点

  1. remove 方法matchValue是false、value是null

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) { //key所在的数组有值,p赋值头结点
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k)))) //如果头结点就是所要找的节点
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);  //下一个节点是树形结构,找到对应节点
            else {
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) { //找到链表里key对应的节点
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null); //没找到则循环查找下一个节点
            }
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {  //找到的node不为空
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); //树形结构删除节点
            else if (node == p)
                tab[index] = node.next;   //如果找到的node是头结点,将头结点的next指向node的下一个节点
            else
                p.next = node.next;   //不是头节点,使用链表删除方法删除节点
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;   //把找到要删除的节点返回
        }
    }
    return null;
}

HashMap的get方法

get方法相对简单,原理也就是在链表里查找或树里查找

public V get(Object key) {
    Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}

/**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {  //找到key对应数组下标的头结点
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first; //如果头结点key和查找的key一样则返回头结点
        if ((e = first.next) != null) { //找下一个节点
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key); //是树形结构则从树里找
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;  //在链表里挨个查找下一个,直到key匹配
            } while ((e = e.next) != null);
        }
    }
    return null; //啥也没找到返回null
}

思考:

这里留给大家思考两个数据结构的问题

为什么是数组+链表(树)的结构?

为什么要转成树形结构?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/173760.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Git】IDEA 集成 GitHub

8、IDEA 集成 GitHub 8.1、设置 GitHub 账号 如果出现 401 等情况连接不上的&#xff0c;是因为网络原因&#xff0c;可以使用以下方式连接&#xff1a; 然后去 GitHub 账户上设置 token。 点击生成 token。 复制红框中的字符串到 idea 中。 点击登录。 8.2、分享工程到 GitHu…

【甄选靶场】Vulnhub百个项目渗透——项目五十五:SP-LEOPOLD v1.2(beef联动msf,脏牛提权)

Vulnhub百个项目渗透 Vulnhub百个项目渗透——项目五十五&#xff1a;SP-LEOPOLD v1.2&#xff08;beef联动msf&#xff0c;脏牛提权&#xff09; &#x1f525;系列专栏&#xff1a;Vulnhub百个项目渗透 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&am…

ZooKeeper-集群搭建

5)ZooKeeper 集群搭建 5.1)Zookeeper集群介绍 Leader选举&#xff1a; •Serverid&#xff1a;服务器ID 比如有三台服务器&#xff0c;编号分别是1,2,3。 编号越大在选择算法中的权重越大。 •Zxid&#xff1a;数据ID 服务器中存放的最大数据ID.值越大说明数据 越新&…

剑指offer—day1.用两个栈实现队列、包含min函数的栈

1.用两个栈实现队列 本题来源&#xff1a;力扣 剑指 Offer 09. 用两个栈实现队列 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/题目描述 用两个栈实现一个队列。队列的声明如下&#xff0c;请实现它的两…

IP-guard如何映射到外网登录访问管理

终端安全管理(endpoint security management)是一种保护网络安全的策略式方法&#xff0c;它需要终端设备在得到访问网络资源的许可之前遵从特定的标准。随着企业信息化发展&#xff0c;终端安全管理系统需求不断扩大&#xff0c;相关系统软件被广泛应用。 IPguard即IP-guard&a…

线段树(重要!多加理解懒惰标记!)

基础概念&#xff1a; 线段&#xff08;区间&#xff09;[L,R] 所对应的线段树是由区间 [L,R] 及其子区间构成的二叉树&#xff08;如下图所示&#xff09; 线段树具有的特性&#xff1a; &#xff08;1&#xff09;线段树的叶结点为只有一个元素的区间&#xff0c;因此长度为…

最新版海豚调度dolphinscheduler-3.1.3安装部署详细教程

0 背景 本文基于Ambari集群搭建最新版本的海豚调度dolphinscheduler-3.1.3版本&#xff0c;后续会尝试整合到Ambari中。 1 安装准备 安装dolphinscheduler需要在环境中安装如下依赖 ① JDK8 下载JDK (1.8)&#xff0c;安装并配置 JAVA_HOME 环境变量&#xff0c;并将其下的 …

用 22 张照片打开 23 年

魔幻又带有现实主义色彩的三年似乎终将见底。这也为 2023 年赋予了一些新的意义&#xff0c;或许是充满生机、怀揣希望、满怀爱意&#xff0c;或许是重新启航、步履不停、勇敢探索……为此&#xff0c;我们收集了 22 位社区用户和公司小伙伴在过去一年的「特别 Moment」及新年愿…

你认为DAO是否可行?新年计划,卯足干劲,兔必No.1

文章目录&#x1f31f; 课前小差&#x1f31f; 聚沙成塔&#x1f31f; 社会价值&#x1f31f; DAO是什么&#x1f31f; 国产化&#x1f31f; 商业化回报&#x1f31f; 写在最后&#x1f31f; 课前小差 哈喽&#xff0c;大家好&#xff0c;我是几何心凉&#xff0c;这是一份全新…

Spring高级之BeanFactory功能

首先&#xff0c;我们想要知道一个接口有哪些功能&#xff0c;就必须要看这个接口的源代码&#xff0c;在idea中&#xff0c;选中这个接口CtrlF12&#xff0c;来查看这个接口里面有哪些方法&#xff1a; 表面上来看&#xff0c;功能其实很少&#xff0c;查看源码及其方法功能 …

来吧,Jenkins+git+mvn+shell一键部署实践起来

环境&#xff1a;centosJenkins-2.319系统自带gitmvn3.8.7jdk1.8一、安装jdk1、https://blog.csdn.net/codedz/article/details/124044974centos自带了openjdk&#xff0c;我是选择自己重新搞一个&#xff0c;用的上面链接地址的yum安装方式2、安装完成查看版本查看java安装路径…

优思学院|质量人对控制图中的规格线和控制线傻傻分不清?

质量人、六西格玛[1]人和很多不同类型的工程师都需要了解什么是控制图&#xff0c;而在控制图中的规格限制&#xff08;Specification Limit&#xff09;"和"控制限制&#xff08;Control Limit&#xff09;"原来对好多人来说都是傻傻分不清&#xff01; 规格限…

线段树入门

对于一个区间进行询问&#xff0c;进行修改&#xff0c;都是用线段树进行处理。线段树和普通的树不一样&#xff0c;普通的树的节点存的是一个编号&#xff0c;线段树存的是一个区间&#xff0c;而且线段树一定是一棵完全二叉树。例如&#xff1a;这就是一棵线段树。例如对于[1…

【Ajax】数据交换格式XML 和 JSON

一、什么是数据交换格式数据交换格式&#xff0c;就是服务器端与客户端之间进行数据传输与交换的格式。前端领域&#xff0c;经常提及的两种数据交换格式分别是 XML 和 JSON。其中 XML 用的非常少&#xff0c;所以&#xff0c;我们重点要学习的数据交换格式就是 JSON。二、XML1…

让交互更加生动!巧用CSS实现鼠标跟随 3D 旋转效果

简单分析一下&#xff0c;这个交互效果主要有两个核心&#xff1a; 借助了 CSS 3D 的能力 元素的旋转需要和鼠标的移动相结合 本人简单的说一下如何使用纯 CSS 实现类似的交互效果&#xff0c;以及&#xff0c;借助 JavaScript 绑定鼠标事件&#xff0c;快速还原上述效果。 …

数据结构---set篇

第一次超时是因为用memsetmemsetmemset不得不超时&#xff0c;第二次超时是我用vectorvectorvector数组的时候&#xff0c;然后以O(n)O(n)O(n)复杂度查找元素之后使用eraseeraseerase方法进行删除&#xff0c;第三次超时是我把查找元素改成了O(logn)O(logn)O(logn)之后用vector…

epoll的ET和LT模式

简介 epoll对fd的操作有两种模式&#xff1a;LT(Level Trigger&#xff0c;水平触发)模式&#xff0c;和ET&#xff08;Edge Trigger,边缘触发&#xff09;模式。 LT 模式是默认的工作模式&#xff0c;这种模式下&#xff0c;epoll相当于一个效率较高的poll&#xff1b; ET模…

89. 注意力机制以及代码实现Nadaraya-Waston 核回归

1. 心理学 动物需要在复杂环境下有效关注值得注意的点心理学框架&#xff1a;人类根据随意线索和不随意线索选择注意点 随意&#xff1a;随着自己的意识&#xff0c;有点强调主观能动性的意味。 2. 注意力机制 2. 非参注意力池化层 3. Nadaraya-Waston 核回归 4. 参数化的注意…

Downie4.6.4视频下载工具

前言 Downie是Mac下一个简单的下载管理器&#xff0c;可以让您快速将不同的视频网站上的视频下载并保存到电脑磁盘里然后使用您的默认媒体播放器观看它们。 下载 Downie 解压后直接安装 主要特点 支持许多网站目前支持超过1,000个不同的网站&#xff08;包括YouTube&#…

Linux | 浅谈Shell运行原理【王婆竟是资本家】

文章目录&#x1f4a7;Shell的运行原理&#x1f449;Shell的基本概念与作用&#x1f449;原理的展示与剖析&#x1f449;Shell外壳感性理解【一门亲事】&#x1f4a7;总结&#x1f4a7;Shell的运行原理 &#x1f449;Shell的基本概念与作用 Linux严格意义上说的是一个操作系统…