【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(上)

news2024/11/16 12:55:46

HashMap工作原理全揭秘 — 核心源码解析

  • 知识盲点
  • 概念介绍
  • 数据结构
    • 数组
    • 链表
    • 数组VS链表
    • 哈希表
    • 不同JVM版本HashMap的展现形式
  • HashMap VS HashTable
    • 特性区别对比
  • hashcode
    • hashCode的作用
    • equals方法和hashcode的关系
    • key为null怎么办
    • 执行步骤
  • 核心参数
    • 容量探讨
    • 负载因子探讨
      • 加载因子过高
        • 加载因子与空间开销
        • 查询成本与加载因子
        • 减少扩容次数和成本
          • 设置初始容量与加载因子
          • 总结

知识盲点

在这里插入图片描述

概念介绍

HashMap是基于Map接口构建的数据结构,它以键值对的形式存储元素,允许键和值都为null。由于键的唯一性,HashMap中只能有一个键为null。HashMap的特点是元素的无序性和不重复性。

注意,HashMap并不是线程安全的。在多线程环境下,如果不进行适当的同步处理,可能会导致数据不一致或其他并发问题。因此,对于需要高并发访问的场景,建议使用线程安全的替代方案,如ConcurrentHashMap

数据结构

在HashMap的数据结构中,数组和链表是核心组件,但它们在实现上有着根本性的差异。

  • 数组是静态的,一旦创建,其大小就无法改变
    • 数组由于其固定的大小,对于大量数据的处理可能会遇到性能瓶颈。
  • 链表是动态的,可以根据需要随时添加或删除节点。
    • 链表则可以灵活地扩展,更好地应对数据增长的需求,链表在内存使用上可能更加碎片化,因为需要为新节点分配空间并在不再需要时进行回收。

数组

数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

链表

链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

数组VS链表

  • 数组的特点:查询效率高,插入和删除效率低
  • 链表的特点:查询效率低,插入和删除效率高

哈希表

综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?这就是我们要提起的哈希表。哈希表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。

非同步和允许使用null之外,HashMap类与Hashtable大致相同。此类不保证映射的顺序,特别是它不保证该顺序恒久不变发生扩容时,元素位置会重新分配

不同JVM版本HashMap的展现形式

JDK8之后的版本,HashMap底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题(循环死锁问题),使的查询和插入,删除的效率都很高。
在这里插入图片描述
HashMap的散列表是懒加载机制,在第一次put的时候才会创建hash表

HashMap VS HashTable

在多线程环境中,HashMap由于其非线程安全的特性,性能可能更高。相比之下,Hashtable通过在实现方法中添加synchronized关键字确保线程安全,因此在性能上可能稍逊一筹。

如果没有特殊需求,建议在常规使用中选择HashMap,多线程环境下,如果需要线程安全的集合,可以使用Collections.synchronizedMap()方法将HashMap转换为线程安全的集合

特性区别对比

在这里插入图片描述

  • 是否允许键为空:值得一提的是,HashMap允许键为null,而Hashtable的键则不可为null。

  • 继承结构的不同 :HashMap是对Map接口的直接实现,而Hashtable不仅实现了Map接口,还继承了Dictionary抽象类。

    • 在这里插入图片描述
    • 在这里插入图片描述
  • 扩充数据量不同 :关于初始容量和扩容策略,HashMap的初始容量为16,而Hashtable的初始容量为11。两者的填充因子默认都是0.75。当需要扩容时,HashMap的容量会翻倍,即capacity * 2; 而Hashtable的容量会在原有基础上增加1,即capacity * 2 + 1。

  • 数据安全的问题 :在单线程环境下或对性能要求较高的场景中,HashMap可能是一个更好的选择。而在多线程环境中,如果需要确保线程安全,则应考虑使用Hashtable或通过Collections.synchronizedMap()方法将HashMap转换为线程安全的集合。

hashcode

在HashMap中,当我们要存储一个键值对时,首先会调用对象的hashCode()方法来获取哈希码。这个哈希码的主要目的是为了确定对象在哈希表中的位置。

为了得到一个更均匀的分布,提高查找效率,hashCode()返回的整数会经过一系列的位操作(如右移和异或)来进一步处理。这些操作的主要目的是为了打乱哈希码的高位和低位,使得不同的键产生的哈希码有更好的随机性,从而减少冲突的可能性。

hashCode的作用

hashCode的存在主要是为了查找的快捷性, hashCode是用来在散列存储结构中确定对象的存储地址的 (用hashcode来代表对象在hash表中的位置) 。

hashCode存在的重要的原因之一就是在HashMap(HashSet其实就是HashMap)中使用(其实Object类的hashCode方法注释已经说明了)。

HashMap之所以速度快,因为他使用的是散列表,根据key的hashcode值生成数组下标(通过内存地址直接查找,不需要判断,但是需要多出很多内存,相当于以空间换时间)

equals方法和hashcode的关系

若重写了equals(Object obj)方法,则有必要重写hashCode()方法
在这里插入图片描述

  • 若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数
  • 若两个对象equals(Object obj)返回false,则hashCode()不一定返回不同的int数
  • 若两个对象hashCode()返回相同int数,则equals(Object obj)不一定返回true
  • 若两个对象hashCode()返回不同int数,则equals(Object obj)一定返回false

同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。

key为null怎么办

key为null的时候,只会放在hashMap的0位置(即key的hashCode为0,对数组长度取余后的下标也是0),不会有链表在HashMap源码中对put方法对null做了处理。

  1. key为null的判断后进入putForNullKey(V value)这个方法,里面for循环是在table[0]链表中查找key为null的元素。

  2. 如果找到,则将value重新赋值给这个元素的value,并返回原来的value。如果没找到则将这个元素添加到table[0]链表的表头。

执行步骤

  • 计算原始哈希码:调用对象的hashCode()方法来获取一个原始的哈希码。
  • 计算哈希表索引:对原始哈希码进行位操作(如右移和异或),与Bucket大小进行取模,得到一个最终的哈希表索引。这个索引用于确定对象在哈希表中的位置。

核心参数

HashMap的实例有两个参数影响其性能:初始容量和加载因子。

  • 容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。
  • 加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度

迭代collection视图所需的时间与HashMap实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。

容量探讨

HashMap的最小树形化容量,这个值的意义是:位桶(bin)处的数据要采用红黑树结构进行存储时,整个Table的最小容量(存储方式由链表转成红黑树的容量的最小阈值)当哈希表中的容量大于这个值时,表中的桶才能进行树形化,否则桶内元素太多时会扩容,而不是树形化为了避免进行扩容、树形化选择的冲突,这个值不能小于4 * TREEIFY_THRESHOLD(16)

如果很多映射关系要存储在HashMap实例中,则相对于按需执行自动的rehash操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。

负载因子探讨

加载因子是用于控制哈希表中元素数量与内部数组大小之间关系的参数。

加载因子过高

加载因子越高,哈希表中的元素数量可以更多,但同时可能导致更多的冲突,从而增加查询成本。

加载因子与空间开销

当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行rehash操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数,通常,默认加载因子(0.75)在时间和空间成本上寻求一种折衷。

当加载因子设置得较高时,哈希表中的元素数量可以更多,从而减少了当内部数组需要扩容时所浪费的空间。这似乎是节省了空间,但实际上,这也意味着更高的冲突可能性。

查询成本与加载因子

当哈希表中的元素数量增加时,发生冲突的可能性也增加。这意味着查找特定键的时间会增加,因为可能需要遍历更长的链表(或红黑树,如果链表长度过长)。因此,高的加载因子会增加查询成本。

减少扩容次数和成本
设置初始容量与加载因子

在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数,如果初始容量大于最大条目数除以加载因子,则不会发生rehash操作。

  • 减少扩容的次数:如果你预计哈希表将包含大量元素,那么选择一个较大的初始容量可能是一个好主意。

  • 较大的初始容量:如果初始容量大于(最大条目数除以加载因子),那么不会发生rehash操作。这意味着,为了减少rehash次数,你可能需要选择一个较大的初始容量。

总结

加载因子是一个权衡参数。高的加载因子可以减少空间浪费,但可能会增加查询成本和rehash操作的次数。

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

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

相关文章

基于pytorch的循环神经网络情感分析系统

任务目标 基于给定数据集,进行数据预处理,搭建以LSTM为基本单元的模型,以Adam优化器对模型进行训练,使用训练后的模型进行预测并计算预测分类的准确率。 数据简介 IMDB数据集是一个对电影评论标注为正向评论与负向评论的数据集…

【Android开发】不同Activity之间的数据回传实例(一)摘桃子游戏

一、功能介绍 该项目实现的功能主要有: 在首页显示一个按钮点击该按钮跳转到桃园页面在桃园页面,点击桃子会弹窗显示摘到几个桃子,同时被点击桃子消失,总桃子数1点击退出桃园会返回首页,首页桃子数会根据点击的桃子数…

伐木工 - 华为OD统一考试

OD统一考试 题解: Java / Python / C++ 题目描述 一根X米长的树木,伐木工切割成不同长度的木材后进行交易,交易价格为每根木头长度的乘积。规定切割后的每根木头长度都为正整数,也可以不切割,直接拿整根树木进行交易。请问伐木工如何尽量少的切割,才能使收益最大化? 输…

一、docker的安装与踩坑

目录 一、安装docker(centos7安装docker)1.安装环境前期准备2.参考官网安装前准备3.参考官网安装步骤开始安装docker4.运行首个容器 二、安装一些软件的踩坑1.启动docker踩坑2.安装mysql踩坑3.罕见问题 三、关于我的虚拟机 一、安装docker(ce…

销售团队如何实现业绩增长?CRM系统的线索管理功能有什么用?

随着“以客户为中心”观念的逐渐普及,销售团队的客户比过去更复杂,交易周期更久,竞争也更激烈。假如没有明确的销售计划,团队可能陷入混乱,最后导致客户&公司之间的负面结果。在这种情况下,人工智能驱动…

FineBI实战项目一(21):不同支付方式订单总额分析开发

点击新建组件,创建不同支付方式订单总额组件。 选择饼图,拖拽total_money到角度,拖拽pay_type到颜色,调节内径。 修改颜色的标识文字。 将组件拖拽到仪表板。 结果如下:

时至今日,编制与大厂到底怎么选?

我觉得这可能是一个辩论三天三夜也不会有结论的话题。 说实话,2023年已经过去,真的没有感觉赚钱更容易,反而是周边失业的同事不少。 现在感觉,是不是选择早点进入编制可能更加稳定?你们又怎么看?

Java中的多线程

进程和线程的概念 进程是应用程序的执行实例有独立的内存空间和系统资源。 线程是进程中执行运算的最小单位,可完成一个独立的顺序控制流程 一。一个进程可以包含多个线程,每个线程都独立执行特定的任务, 是CPU调度和分派的基本单位。 多线…

【AT 指令开发】软件框架与接口

目录 1 软件逻辑2.代码2.1 at_command.h2.2 at_command.c本文主要用于记录纯MCU无OS下,AT 指令开发软件框架 1 软件逻辑 2.代码 2.1 at_command.h #ifndef AT_COMMAND_H #define AT_COMMAND_Hvoid AT_CMD_Process(uint8_t *uartBuffer, uint8_t dataLen);/*描述AT指令返回值…

第二百五十九回

文章目录 知识回顾示例代码经验总结 我们在上一章回中介绍了MethodChannel的使用方法,本章回中将介绍EventChannel的使用方法.闲话休提,让我们一起Talk Flutter吧。 知识回顾 我们在前面章回中介绍了通道的概念和作用,并且提到了通道有不同的…

PyCharm中配置安装PyQt5、QtDesigner

PyCharm中配置安装PyQt5 使用 pip install PyQt5 命令安装。 安装pyqt5-tools:pip install pyqt5-tools 安装PyQt5Designer:pip install PyQt5Designer 上述三个都安装好之后,输入 pip list 查看一下 有如下内容就安装成功啦!…

四、C++内存管理

1 C/C内存分布 在学习C的内存管理方式之前&#xff0c;我们先来看一道有关C/C内存分布的题目&#xff1a; 阅读下面的代码&#xff0c;回答相关问题&#xff1a; #include <iostream> using namespace std; int globalVar 1; static int staticGlobalVar 1; int main…

1.6计算机网络的性能指标

1.6计算机网络的性能指标 常用的计算机网络的性能指标有7个&#xff1a;速率、带宽、吞吐量、时延、往返时间、利用率、丢包率 1.6.1速率 计算机发送的信号是以二进制数字形式的。一个二进制数字就是一个比特(bit&#xff0c;binary digit)字节:Byte&#xff0c;1Byte8bit(1…

在海绵城市建设中,低功耗遥测终端有哪些独特的优势?

近年来&#xff0c;随着物联网技术的迅猛发展&#xff0c;数据监测和传输已经成为各行各业不可或缺的环节。在诸多特殊环境中因供电问题、潮湿、不便进入等诸多原因&#xff0c;需要一款功耗低、数据传输稳定&#xff0c;防潮抗锈蚀的低功耗遥测终端。 为满足这一需求&#xf…

太强了!腾讯开源!多模态AppAgent自主操作智能手机应用程序!

AppAgent是一款基于大型语言模型&#xff08;LLMs&#xff09;的新型多模态智能代理框架&#xff0c;专为操作智能手机应用而设计。它结合了GPT-4V的先进视觉理解能力&#xff0c;通过“眼睛”观察手机界面&#xff0c;模仿人类的点击和滑动交互方式来学习操作应用程序。这种方…

小红书种草类型有哪些,小红书营销攻略

我们都知道小红书是个内容平台。用户来这可以看到各种类型的笔记&#xff0c;从笔记中获取自己想要了解的内容。这也就意味着平台上有着许多种不同的笔记类型。今天我们和大家分享下小红书种草类型有哪些&#xff0c;小红书营销攻略&#xff01; 1. 明星带货类 顾名思义&#x…

vivado IP Revision Control

2020.2 只需要git 管理 prj.xpr 和 prj.srcs/ https://china.xilinx.com/video/hardware/ip-revision-control.html https://www.xilinx.com/video/hardware/vivado-design-suite-revision-control.html

网络编程的理论基础

文章目录 1 重点知识2 应用层3 再谈 "协议"4 HTTP协议4.1 认识URL4.2 urlencode和urldecode4.3 HTTP协议格式4.4 HTTP的方法4.5 HTTP的状态码4.6 HTTP常见Header4.7 最简单的HTTP服务器 3 传输层4 再谈端口号4.1 端口号范围划分4.2 认识知名端口号(Well-Know Port Nu…

EOCR电机保护器485通讯协议概念

Modbus是由Modicon&#xff08;现为施耐德电气公司的一个品牌&#xff09;在1979年发明的&#xff0c;是全球第一个真正用于工业现场的总线协议。为更好地普及和推动Modbus在基于以太网上的分布式应用&#xff0c;目前施耐德公司已将Modbus协议的所有权移交给IDA&#xff08;In…

IMS中如何区分initial INVITE和re-INVITE?

这里就要先看下Dialog的定义。 dialog是两个UA之间持续一段时间的点对点 SIP关系。dialog通过SIP消息建立&#xff0c;例如对 INVITE request的 2xx response。dialog由Call-ID、local tag和remote tag来区分&#xff0c;也就是Call-ID 、from-tag和to-tag就可以确定一个dialog…