再探Java集合系列—HashMap

前面我们已经针对LinkedList和ArrayList的底层原理进行了具体研究讨论,大家可以跳链接阅读哦~

再探Java集合系列—ArrayList-CSDN博客

再探Java集合系列—LinkedList-CSDN博客

HashMap有哪些特征呢?

  • value可以重复,key不能重复,允许null键和null值(如果新添加key-value的Map中已经存在重复的key,那么新添加的value就会覆盖该key原来对应的value)
  • 不能保证key-value对的顺序
  • 如果添加相同的key,则会覆盖原来的key-val,等同于修改
  • 没有实现同步,因此线程不安全,方法没有做同步互斥操作,

如何使用HashMap呢?

Map<k,v> map =new HashMap<k,v>();

Map是一种键-值对(key-value)集合,Map中每一个元素都包含一个键对象和一个值对象。

元素:键-值对整体

因为Map中的key和value是不允许使用基本类型的,那为什么呢?

Java中的基本类型和包装类型之间存在自动装箱(autoboxing)和拆箱(unboxing)的过程。当我们将基本类型的值放入Map中时,Java会自动将其转换为对应的包装类型;当我们从Map中取出包装类型的值时,Java会自动将其转换为对应的基本类型。这个过程会带来一些性能上的损耗,特别是在大量数据操作时

Map有哪些方法?

  • put(key,value):添加数据
  • get(key,value):根据key取值
  • containsKey(key):判断当前的map集合是否包含指定的key
  • containsValue(value):判断当前的map集合是否包含指定的value
  • clear:清空集合
  • keySet():获取map集合的key的集合
  • values():获取集合的所有value的值
  • for(String key:keys):遍历map集合

实战演练

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapTest {
    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<String,Integer>();
        //添加数据
        map.put("b",1);
        map.put("c",2);
        map.put("e",2);
        System.out.println(map);   //输出结果:{b=1, c=2, e=2}

        //根据key取值
        System.out.println(map.get("b")); //输出结果:1

        //根据key移除键值对
        map.remove("c");
        System.out.println(map);  //输出结果:{b=1, e=2}

        //map集合的长度
        System.out.println(map.size());  //输出结果:2

        //判断当前的map集合是否包含指定的key
        System.out.println(map.containsKey("b"));  //输出结果:true
        //判断当前的map集合是否包含指定的Value(书写敏感,加双引号和不加双引号的区别)
        System.out.println(map.containsValue(1));  //输出结果:true
        System.out.println(map.containsValue("1"));  //输出结果:false

        //清空集合
        //map.clear();

        //获取map集合的key的集合
        map.keySet();
        //获取集合的所有value值
        map.values();


        //获取map集合的key的集合
        Set<String> keys = map.keySet();
        //遍历map集合
        // 第一种方式:for循环
        for(String key:keys){
            System.out.println("key:"+key+",value:"+map.get(key));
        }

       //第二种方式:通过map.entrySet();遍历map集合
        Set<Map.Entry<String,Integer>> entrys = map.entrySet();
        for (Map.Entry<String,Integer> en:entrys){
            System.out.println("key:"+en.getKey()+",value:"+en.getValue());
        }
    }
}

数据结构

jdk1.8前:数组+链表 无序,头插

jdk1.8 :数组+链表+红黑树 无序,尾插


底层原理

实例化HashMap

在创建HashMap对象的时候知识初始化一些参数

为什么默认初始容量为16?

匹配硬件设计

大量数据测试,确定基本参数配置


增加元素—put

(图片来源于网上)

当我们在put添加元素的时候,当多个元素映射到了同一个位置,hashmap中采用单链表的形式链接每一个元素,在jdk7使用头插法,jdk8开始使用尾插法,同一个位置会将要插入的新元素放在链表的头部。并且总是会判断当前table的容量是否满足可使用范围

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;  //n为table容量,i为下标           
    
        if ((tab = table) == null || (n = tab.length) == 0)   //如果table为null  或者  table长度==0
            n = (tab = resize()).length;  //进行第一次扩容,n为第一次扩容后的table长度
    
        if ((p = tab[i = (n - 1) & hash]) == null)   //计算节点要插入的位置,如果p为null表示这个位置为空,则创建一个新节点插入
            tab[i] = newNode(hash, key, value, null);   //在table表的置顶索引位置处插入新节点
        else {  //否则,说明要映射的索引位置已经存在了元素
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))  
                //判断要插入的节点是否已存在,如果存在则直接覆盖原来的key-value
                e = p;
                
            else if (p instanceof TreeNode)    //判断该链是不是红黑树
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);   
                
            else {        //否则,不是红黑树
                for (int binCount = 0; ; ++binCount) {  //遍历该链表表
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st   //判断该链表长度是大于8则转换为红黑树进行处理
                            treeifyBin(tab, hash);
                        break;
                    }

                    //如果key已经存在则直接覆盖value
                    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;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;

    //超过最大容量则扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

如何确定key的位置的?(哈希函数)

先计算出key对应的hashcode(32位的int类型数),将hashcode的高16位和低16位进行异或运算(相同为0,不同为1)

Key值相同会覆盖

对于HashMap来说key必须是唯一的,插入的时候如果已经存在相同的key则会把原来的值覆盖。如下面的例子

无序插入

前面说到了HashMap可以插入key为null的键值对,我们模拟一下看看:我在put的时候最后一个元素的时候key和value都为null,但输出结果来看key为null的键值对是在集合的第一个,因此我们可以总结出HashMap插入顺序是无序的

HashMap如何解决哈希冲突的?

将相同哈希值的键值对通过链表(拉链法)+红黑树的形式存放起来


扩容机制——resize()

思想:扩容数组容量+重新计算hash值

什么时候需要扩容?

table容量>=容量*加载因子(0.75)

第一次扩容:16*0.75=12,当临界值为12的时候

,第一次扩容之后的table容量为 32,临界值为24

第二次开始及之后:容量为原来容量的2倍,临界值为原来的2倍,第二次扩容后table容量为64,临界值为48,依次类推

为什么加载因子是0.75?

必须是2的几次幂,当加载因子选择了0.75就可以保证它与容量的乘积为证书

什么时候会由链表变成红黑树?(树化思想)

容量先扩容到64,并且链表长度超过 8 的时候,会将链表转化为红黑树来提高查询效率

扩容的流程?

通过 resize 方法来实现的

  1. 将临界值修改为原来临界值的2倍
  2. 将table容量变为原来的table容量的2倍
  3. 创建新数组
  4. 遍历旧数组,将旧数组中的元素复制到新数组中
    1. 确定在新数组中的位置:hashcode / table容量
    2. 如果新位置已经有元素:当前元素添加到链表的末尾
    3. 如果链表长度>8,转成红黑树

注意:在jdk8中扩容的时候会保持链表原来的顺序


存在什么问题?

①、线程安全问题

  • 多线程下扩容会死循环
  • 多线程下 put 会导致元素丢失
  • put 和 get 并发时会导致 get 到 null

解决方案:使用ConcurrentHashMap

整个集合是这一个(table数组)

对node节点的共享变量使用volatile修饰保证可见性

ConcurrentHashMmap在第一次进行put操作的时候才会对进行初始化,在初始化node节点的过程中会调用initTable 方法,判断到如果有其他的线程正在进行初始化则调用yield()让出cpu,并且修改状态为正在初始化状态

CAS(自旋)

②、无序插入

hashmap插入的元素是无序的,如果想要顺序可以使用LinkedhashMap,并且LinkedHashMap继承了HashMap

如果有想要交流的内容欢迎在评论区进行留言,如果这篇文档受到了您的喜欢那就留下你点赞+收藏+评论脚印支持一下博主~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++干货铺】继承 | 多继承 | 虚继承

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 继承的概念及定义 继承的概念 继承的定义 继承基类成员访问方式的变化 基类和派生类的赋值转化 继承中的作用域 派生类的默认成员函数 构造函数 拷贝构造…

解决Flutter运行报错Could not run build/ios/iphoneos/Runner.app

错误场景 更新了IOS的系统版本为最新的17.0, 运行报以下错误 Launching lib/main.dart on iPhone in debug mode... Automatically signing iOS for device deployment using specified development team in Xcode project: GN3DCAF71C Running Xcode build... Xcode build d…

羊大师分析,鲜羊奶对健康的影响与作用

羊大师分析&#xff0c;鲜羊奶对健康的影响与作用 你是否曾经听到过“羊奶比牛奶更健康”的说法&#xff1f;而鲜羊奶作为最纯正的羊奶形式&#xff0c;其营养价值更是不可小觑。除了拥有传统奶类所包含的营养成分外&#xff0c;鲜羊奶还含有更多人体必需的氨基酸和微量元素&a…

12.1_黑马Redis实战篇Redis优化秒杀Redis消息队列实现异步秒杀

目录 实战篇22 实战篇23 实战篇24 实战篇25 实战篇26 实战篇27 实战篇28 实战篇29 实战篇30 实战篇22 将任务分布给不同的线程去做&#xff0c;可以加快程序运行速度。 放到lua脚本&#xff0c;保证原子性。同时&#xff0c;这样的优化&#xff0c;可以减轻数据库的压…

《尚品甄选》:后台系统——商品管理,对商品数据进行维护(debug一遍)

文章目录 一、表结构介绍二、列表查询三、添加功能(复杂)3.1 加载品牌数据3.2 加载商品单元数据3.3 加载商品规格数据3.4 保存商品数据 四、修改功能4.1 查询商品详情4.2 保存修改数据 五、删除商品六、商品审核七、商品上下架 一、表结构介绍 商品管理就是对电商项目中所涉及…

基于H5“汉函谷关起点新安县旅游信息系统”设计与实现

目 录 摘 要 1 ABSTRACT 2 第1章 绪论 3 1.1 系统开发背景及意义 3 1.2 系统开发的目标 3 第2章 主要开发技术介绍 5 2.1 H5技术介绍 5 2.2 Visual Studio 技术介绍 5 2.3 SQL Server数据库技术介绍 6 第3章 系统分析与设计 7 3.1 可行性分析 7 3.1.1 技术可行性 7 3.1.2 操作…

办公软件PDF转换工具 - Bruce的PDF工具pdftool

Bruce的PDF工具 - 办公软件PDF转换工具 - pdftool&#xff0c;支持&#xff1a; 1、图片转PDF&#xff0c;支持图片自动压缩&#xff0c;可预览图片 2、合并PDF&#xff0c;支持多个PDF合并成一个PDF 3、PDF转图片&#xff0c;PDF的每页转成一张图片 4、OFD转PDF&#xff0c;O…

Postman:专业API测试工具,提升Mac用户体验

如果你是一名开发人员或测试工程师&#xff0c;那么你一定知道Postman。这是一个广泛使用的API测试工具&#xff0c;适用于Windows、Mac和Linux系统。今天&#xff0c;我们要重点介绍Postman的Mac版本&#xff0c;以及为什么它是你进行API测试的理想选择。 一、强大的功能和易…

honle电源维修UV电源控制器EVG EPS40C-HMI

好乐UV电源控制器维修&#xff1b;honle控制器维修&#xff1b;UV电源维修MUC-Steuermodul 2 LΛmpen D-82166 主要维修型号&#xff1a; EVG EPS 60/120、EVG EPS 100、EVG EPS200、EVG EPS 220、EVG EPS 340、EVG EPS40C-HMI、EVG EPS60 HONLE好乐uv电源维修故障包括&#…

2023年第十二届数学建模国际赛小美赛B题工业表面缺陷检测求解分析

2023年第十二届数学建模国际赛小美赛 B题 工业表面缺陷检测 原题再现&#xff1a; 金属或塑料制品的表面缺陷不仅影响产品的外观&#xff0c;还可能对产品的性能或耐久性造成严重损害。自动表面异常检测已经成为一个有趣而有前景的研究领域&#xff0c;对视觉检测的应用领域有…

智能优化算法应用:基于平衡优化器算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于平衡优化器算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于平衡优化器算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.平衡优化器算法4.实验参数设定5.算法结果…

Docker容器中的OpenCV:轻松构建可移植的计算机视觉环境

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 构建可移植的计算机视觉环境 文章目录 前言引言简介&#xff1a;目的和重要性&#xff1a; 深入理解Docker和OpenCVDocker的基本概念和优势&#xff1a;OpenCV简介和应用领域&#xff1a;…

几个linux指令提升编程效率

history history命令是Linux/Unix系统中的一个常用命令&#xff0c;用于查看当前用户在命令行中执行过的命令历史记录。该命令允许用户查看、搜索、编辑和执行之前执行过的命令&#xff0c;为用户提供了方便、快捷的操作方式。 查看历史命令&#xff1a; history查看最近n条…

nginx三种虚拟主机的配置(IP,端口,域名)

准备工作&#xff1a; [rootbogon ~]# mkdir -p /data/nginx{1..3} #-p是用于递归创建使用 [rootbogon ~]# echo "hello nginx1" > /data/nginx1/index.html [rootbogon ~]# echo "hello nginx2" > /data/nginx2/index.html [rootbogon ~]# echo &q…

adb环境搭建(adb下载与安装)

文章目录 前言一、adb下载二、adb安装1.将下载的安装包解压缩2.将解压缩后的文件夹放到自己想存放的目录下&#xff08;不要放到带有中文的目录下&#xff09;——我这放到D盘根目录下3.配置环境变量3.1.鼠标放到 "此电脑"→鼠标右击→选择属性3.2.点击 "高级系…

海银・颖奕海南国际健康管理基地启航!“财富+健康”双轮驱动战略加速中

现场&#xff0c;颖奕集团、颖奕生物科技集团董事长凌临贵&#xff0c;海南博鳌乐城国际医疗旅游先行区管理局党委书记、局长贾宁&#xff0c;海银控股董事长韩宏伟&#xff08;从左至右&#xff09;共同启动该项目。 11月24日&#xff0c;“海银颖奕海南国际健康管理基地”在…

正则表达式及文本三剑客grep sed awk

正则表达式 1.元字符 . //匹配任意单个字符&#xff0c;可以是个汉字 [yang] //匹配范围内的任意单个字符 [^y] //匹配处理指定范围外的任意单个字符 [:alnum:] //字母和数字 [:alpha:] //代表…

二叉树的操作(C++实现)

目录 ⚽实现要求&#xff1a; &#x1f3d0;题目分析&#xff1a; &#x1f3c0;代码展示&#xff1a; &#x1f4cc;前提类和函数声明&#xff1a; &#x1f94e;模块一&#xff08;层次—>创建二叉树&#xff09;&#xff1a; &#x1f3b1;模块二&#xff08;三种…

QT Creator 保存(Ctrl+S)时,会将Tab制表符转换为空格

今天在写makefile文件时&#xff0c;发现QT Creator 保存(CtrlS)时&#xff0c;会将Tab制表符转换为空格&#xff0c;之前没有发现&#xff0c;略坑&#xff0c;官网上也有说明&#xff0c;点这里 简单来说&#xff0c;解决办法如下 依次点击&#xff1a;Tools ->Options-&g…

C51--DHT11数据读取

DHT11传输0的时序分析&#xff1a; DHT11传输1的时序分析&#xff1a; 用while(dht)卡点&#xff0c;当不满足while时&#xff0c;信号拉低&#xff1b; 用while(&#xff01;dht)卡点&#xff0c;当不满足while时&#xff0c;信号拉高。 传输0和1时有效数据都是高电平&…