HashMap 的特点及其优缺点以及底层实现

news2024/11/11 8:01:56

Hash:散列
Map:映射
顾名思义,是以 key-value 的形式存储数据

public class HashMap<K,V> {
	transient Node<K,V>[] table;
	// 初始容量 16
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
}

通过源码可知,他是一个类型为 key-value 形式的数组,key 的 hash(不仅仅是取hash这么简单,后续会讲)值决定了数据应该存放在数组的哪个下标里面,存放在数组里面的数据格式为链表,在 jdk1.8 中极其之后,数据格式引入了树型结构,会在一些特殊情况下发生链表与红黑树的相互转换

HashMap 的特点

无序

存储的顺序和取出的顺序不一定一样,这也是他的缺点

key可以为null

当 key 为 null 时,默认 hash 值为0,所以说可以存储 key 为 null 的数据

key不可以重复

相同的 key 数据会被覆盖,这是符合正常的认知的

hash冲突

为什么会产生 hash 冲突呢,前面讲过,key 的 hash 值决定了数据应该存放在数组的哪个下标里面
而为了防止数组下标越界,那么这个 hash 的计算方式需要保证 hash 的取值范围在数组的下标范围内
这样就会产生不同的 key 计算出相同的 hash,这就是 hash 冲突,也叫 hash 碰撞

如果直接进行 value 的覆盖操作,那么就会出问题,所以说在 key 的 hash 值相同,key 不同时,直接挂在了当前下标中的链表的下一个位置

如下图所示,链表中存储的 Node 节点包含四个数据:key、value、hash、下一个 Node

在这里插入图片描述
那么这个具体的 hash 值是怎么算的呢,有没有一个巧妙的算法能让计算出来的 hash 值刚好在数组中间且不会出现数组越界呢?

有一个好的方式就是先算出 key 的真实 hash 值,然后根据数组的长度取模

直接用长度的取模出来的值在1 - size 之间,0永远不会取到且取到 size 时又越界了,所以说直接数组的长度 -1 取模就好了,这是有些人不明白为什么减一的原因

但是在 HashMap 中,并没有采取以上说的取模的操作,而是用了更取巧的操作,与运算

不管真正的 hash 值是多少,忽略前面的具体值,只取最后四位二进制值进行与运算,还需要确保在 0000 - 1111 之间,这也需要数组的长度必须是2的整次方数,比如 2、4、8、16、32等,这也解释了为什么 HashMap 中的数组长度默认是 16,以及每次扩容都是在之前的基础上乘2的原因

那么如果仅仅是对 key 的值进行 hash 算法然后取后四位,那么发生碰撞的概率依然很大,会出现很多值存在同一个链表上,其他链表没有值,所以说不仅仅是 hash 计算,而且进行了二次 hash,也叫做扰动函数,目的是让更多位置参与到 hash 计算中来,破坏掉直接 hash 的扎堆概率

树 、链 转化

在 jdk1.8 中,数组中的每个下标存储的链表,在链表长度大于8且数组容量 >= 64 时,链表中的数据会进行树化(红黑树,是一种自平衡的二叉树),主要是为了提升查找速度,链表一旦长了查询就会很慢;

当链表长度小于8时,又会从红黑树转化为链表,当数据量少的时候,链表与红黑树的查询速度不相上下,且在新增元素的时候,链表不需要计算节点的位置(红黑树是需要计算的)

相互转化可以形成很好的互补

扩容

扩容因子,也可以说是满载率,默认是 0.75,

具体来说当元素超过整体空间 75% 的时候,并且新元素要添加的数组位置不为空的时候就要进行数组长度扩容了

也就是说当元素超过整体空间 75% 的时候并不一定会触发扩容,还需要看新元素要添加的位置是否有值存在,如果没有是不会进行扩容的

HashMap 的创建与常用方法

put方法

在 put 中才开始创建数组

在 jdk1.7 中,元素的添加在链表上采用的是头插法(1.7的设计者认为,新插入的元素在后续的过程中有更大的概率被使用到)

总结一下 HashMap 的存值过程:

  • 先通过二次 hash 得到一个扰动后的 hash 值
  • 然后进行与运算确定需要存放到哪个数组下标的链表当中
  • 然后确定是否需要扩容
    • 不需要扩容则直接头插法插入
    • 需要扩容:
      1. 则创建一个是原来长度2倍的数组
      2. 将之前的数组中的所有元素的 key 根据现在的新数组长度进行新的与运算,只有50%的概率结果会跟之前的相同(key的二进制当中,后四位不会发生改变,只有倒数第五位有可能变化,取值可能性是0、1,所以说是50%)
      3. 然后更新 hash,并将 Node 存储到新数组的新下标对应的链表当中
      4. 然后再进行新的 hash (数组长度改变了)
      5. 最后进行数据的插入操作
get方法

先进行对 key 的二次 hash,然后与运算找到数组的索引,通过索引找到当前索引的链表,在链表中遍历,通过 hsah 值相等且 key 的值也相等,找到 Node 节点的 value

HashMap 缺点

  1. 无序:存储的顺序和取出的顺序不一定一样
  2. 线程不安全的

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

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

相关文章

Web安全 iwebsec 靶场搭建.

Web安全 iwebsec靶场搭建 iwebsec 本质上是一个漏洞集成容器&#xff0c;里面集成了大量的漏洞环境.&#xff08;如&#xff1a;集合了SQL注入、文件包含、命令执行、XXE、反序列化、SSRF、XSS、文件上传等常见的web漏洞环境&#xff09; 目录&#xff1a; 靶场安装步骤&#…

免费gpt-4-国内使用gpt-4

如何用上gpt-4 GPT-4尚未正式发布和公开&#xff0c;因此我们无法提供对GPT-4的具体使用方法。但是&#xff0c;可以从GPT-4的前一代——GPT-3的使用经验和GPT-4的预期功能来看&#xff0c;建议如下&#xff1a; 了解GPT-4的语言处理能力和适用场景&#xff1a;GPT-4预计将进一…

影视动画制作中的后期渲染是什么意思?

影视动画制作是一项非常复杂的任务&#xff0c;需要涵盖从剧本创作到角色设计、场景布置、动画制作、后期渲染等多个环节。其中&#xff0c;后期渲染是制作过程中的最后一步&#xff0c;也是非常重要的一步&#xff0c;它可以使得动画画面更加真实、细腻&#xff0c;达到更好的…

CPU Cache:访问存储速度是如何大幅提升的?

我们了解到不同的物理器件&#xff0c;它们的访问速度是不一样的&#xff1a;速度快的往往代价高、容量小&#xff1b;代价低且容量大的&#xff0c;速度通常比较慢。为了充分发挥各种器件的优点&#xff0c;计算机存储数据的物理器件不会只选择一种&#xff0c;而是以 CPU 为核…

浅析“04.23王者荣耀KPL比赛因出现硬件异常导致比赛延期”这一事件

背景 不知道朋友们有没有看昨天晚上八点多的王者荣耀KPL比赛&#xff08;成都AG超玩会VS广州TTG&#xff09;这一场&#xff0c;当时比赛进行到快15分钟的时候出现了红方请求暂停的情况&#xff0c;后来比赛直播界面就一直提示如下&#xff1a; 本以为这个问题应该不算太严重…

Java——栈的压入,弹出序列

题目链接 牛客网在线oj题——栈的压入,弹出序列 题目描述 输入两个整数序列&#xff0c;第一个序列表示栈的压入顺序&#xff0c;请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序&#xff0c;序列4,5,3,2,1是…

手把手教你PXE高效网络装机、Kickstart无人值守安装(详细版)

目录 一、部署PXE远程安装服务1.1PXE定义1.2PXE服务优点1.3搭建网络体系前提条件1.4 搭建PXE远程安装服务器 二. 实验2.1 服务器操作2.2 安装启动TFTP服务并修改TFTP服务的配置文件2.3 安装并启用DHCP服务2.4 准备linux内核&#xff0c;初始化镜像文件2.5 准备PXE引导程序2.6 安…

编程中最难的就是命名?这几招教你快速上手

作者&#xff1a;陈立(勤仁) 你可不能像给狗狗取名字那样给类、方法、变量命名。仅仅因为它很可爱或者听上去不错。 在写代码的时候&#xff0c;你要经常想着&#xff0c;那个最终维护你代码的人可能将是一个有暴力倾向的疯子&#xff0c;并且他还知道你住在哪里。 01 为什么…

GitHub star最多的 dnmp环境 安装

对于安装GitHub上start最多的dnmp环境的步骤及感悟 https://github.com/yeszao/dnmp/blob/master/README.md 在服务器上装docker与docker-compose 注意&#xff1a;安装docker-compose的时候选择官方版本安装(虽然慢但是请等等)&#xff0c;我操作时出现过国内镜像地址安装但…

flex弹性布局的基本操作知识

今天为大家阐述如何在开发APP或网站的时候&#xff0c;制定一套弹性布局&#xff0c;相互之间兼容&#xff0c;那么我们就可以用Flex来实现&#xff1a; 什么是flex?&#xff1a;Flex是Flexible Box的缩写&#xff0c;意为”弹性布局”&#xff0c;用来为盒状模型提供最大的灵…

Ubuntu 上使用nginx部署vue项目(403/(98: Address already in use))

准备好前端dist文件 保证dist/index.html 点击在本地可以访问&#xff0c; 一&#xff0c;nginx安装 第一步&#xff0c;更新源列表 apt-get update 第二步&#xff0c;安装nginx apt-get install nginx 第三步&#xff0c;检查nginx是否安装成功。如果出现版本号说明安…

直播预告:重保常见攻击场景及解决方案

重保在即&#xff0c;针对邮件系统的网络攻击、主机威胁、账号失陷等攻击场景&#xff0c;该如何应对&#xff1f; 4月25日&#xff08;周二&#xff09;15&#xff1a;00-16&#xff1a;30 Coremail举行重保常见攻击场景及解决方案直播交流会 在这里&#xff0c;您将看到&…

itop-3568 开发板系统编程学习笔记(19)GPIO 应用编程

【北京迅为】嵌入式学习之Linux系统编程篇 https://www.bilibili.com/video/BV1zV411e7Cy/ 个人学习笔记 文章目录 使用 sysfs 方式操作 GPIOGPIO 应用编程 使用 sysfs 方式操作 GPIO 和上一篇笔记 LED 应用编程一样&#xff0c;GPIO 也可以通过 sysfs 方式来控制。 在串口终…

Elasticsearch:使用 Elastic APM 监控 Android 应用程序(一)

作者&#xff1a;Alexander Wert, Cesar Munoz 人们通过私人和专业的移动应用程序在智能手机上处理越来越多的事情。 拥有成千上万甚至数百万的用户&#xff0c;确保出色的性能和可靠性是移动应用程序和相关后端服务的提供商和运营商面临的主要挑战。 了解移动应用程序的行为、…

【计算机视觉】必须了解的图像数据底层技术

计算机视觉的主要目的是让计算机能像人类一样甚至比人类更好地看见和识别世界。计算机视觉通常使用C、Python和MATLAB等编程语言&#xff0c;是增强现实&#xff08;AR&#xff09;的一项重要技术。 文章目录 一、引言二、什么是计算机视觉&#xff08;Computer Vision&#xf…

Flink窗口函数

1.什么是窗口函数 Flink窗口函数是指对数据流中的数据进行分组和聚合操作的函数。 FlinkSQL支持对一个特定的窗口的聚合。例如有用户想统计在过去的1分钟内有多少用户点击了某个的网页。在这种情况下&#xff0c;我们可以定义一个窗口&#xff0c;用来收集最近一分钟内的数据…

codemirror 5前端代码编辑器资料整理。

CodeMirror 是基于js的源代码编辑器组件&#xff0c;它支持javascript等多种高级语言&#xff0c;tampermonkey内置的代码编辑器就是基于它。它的按键组合方式兼容vim&#xff0c;emacs等&#xff0c;调用者还可自定义”自动完成“的列表窗口&#xff0c;自由度极高&#xff0c…

Android studio 按钮状态列表

1.创建一个drawable&#xff0c;类型selector 。 <?xml version"1.0" encoding"utf-8"?> <selector xmlns:android"http://schemas.android.com/apk/res/android"><!--被按下状态 --><item android:state_pressed"…

信息安全复习三:古典密码之设计好的密码算法

一.章节梗概 讨论以下算法&#xff0c;理解怎么设计好的密码算法的关键问题 1.Caesar cipher 2.单字母表密码 3.Playfairmima 4.维吉尼亚密码 5.自动生成密码 二.Caesar cipher 2.1 穷举攻击 穷举攻击定义&#xff1a;尝试所有密钥直到有一个合法密钥能够把密文还原成明文&…

软考软件设计师 操作系统笔记

操作系统地位 程序顺序执行&#xff08;进程管理&#xff09; 程序顺序执行的特征&#xff0c;顺序性封闭性可再现性 前趋图 P1结束后 V操作 SS1 P2操作前先执行S S -1 此时S0 一个箭头对应一个信号量 程序并发执行和前驱图 找到输入i计算c输出p&#xff0c;如果找不到就…