哈希问题详解

news2024/9/22 10:08:03

什么是哈希表

在引入哈希表之前,先谈一下为什么要了解哈希表。在学习Set集合时,发现Set集合可以实现无序存储,那么Set是如何实现的无序存储?

打开源码会发现Set集合的底层实际上是由一个map集合实现的。那么什么是哈希表呢?

哈希表(Hash Table)是一种数据结构,也称之为散列表。我们常用的HashMap集合就是哈希表的一种实现,所以我们通常说Set集合的底层是由哈希表实现的。

对于哈希表,在jdk1.8之前的实现方式是:数组+链表+(哈希算法)
jdk1.8时引入了红黑树结构,此时哈希表=数组+链表+红黑树+(哈希算法)

红黑树,是一种实现高效查找的二叉树。其实现机理是根据元素的hashCode的大小决定存放的位置(小的往左,大的往右)。

再回到我们的问题,Set集合如何实现的无序存储?

我们再向Set集合中添加一个数据时,实际上就是向哈希表中添加元素。我们知道每个元素都有一个native的hashCode()方法,JVM会为每个元素分配一个hashCode

hashCode哈希值:通过哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,这个较小二进制值就是元素的哈希值

获取到哈希值之后根据哈希值进行哈希运算获取存储位置再存储到对应的位置上,以此来实现无序存储

哈希表组成

JDK1.8之前

我们已经知道JDK1.8之前哈希表的组成是数组+链表+(哈希算法),我们以HashSet集合为例打开源码查看

// Set集合底层由map实现  HashSet源码
private transient HashMap<E,Object> map;
// 再找到HashMap源码  可以发现哈希表底层的数组就是Node数组
transient Node<K,V>[] table;

Node<k,v>是HashMap类中的一个静态内部类,我们称之为节点。而哈希表中直接存储的就是node节点,Node的主要作用就是用于存储数据。

// Node类
static class Node<K,V> implements Map.Entry<K,V> {
    // 元素的hashCode
        final int hash;
    // 键
        final K key;
    // 值
        V value;
    // 下一个元素节点
        Node<K,V> next;
    . . .
}

哈希表的数组就体现于此,下面我们看典型的哈希算法实现机理:

首先我们创建一个Set集合并添加数据

HashSet<String> sets = new HashSet<String>();
        sets.add("元素-1");
        sets.add("元素-2");
        sets.add("元素-3");
        sets.add("元素-4");
        sets.add("元素-5");
        sets.add("元素-6");
        sets.add("元素-7");

哈希算法简单实现

假设哈希表底层数组长度为7(容量满后底层会实现扩容),然后我们开始向散列表中添加数据,过程如图
在这里插入图片描述
上图所示,在数据添加的过程中则会产生一种情况:当元素通过哈希算法得到对应的索引位置(可以理解为元素的存储地址)时发现该位置上已经存有元素时,那么当前元素的存储就需要其他方法来解决。这种情况就叫做哈希冲突(后面给到具体讲解),而这里的解决方案就是在对应位置形成链表。

JDK1.8之后

jdk1.8之后引入了红黑树,使散列表查询的性能更佳。红黑树的引用是由开发人员已经再底层封装完毕,并不需要我们去操作。那么红黑树的引用体现在哪里呢?

源码如下:

// 依旧是根据Set集合的add方法依次向上找 
// 最终我们会找到HashMap的putVal方法  
// putVal方法一大坨判断,下面给出红黑树的部分
if ((e = p.next) == null) {
   p.next = newNode(hash, key, value, null);
    // binCount就是我们在数组的某个索引处形成的链表长度
    // TREEIFY_THRESHOLD 是Java规定的链表最大长度
   if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
       // treeifBin将链表转换为树
      treeifyBin(tab, hash);
   break;
}

可以发现当我们的链表长度达到8时,底层就会将链表转换为红黑树存储

哈希冲突

前文已经提到了哈希冲突,哈希冲突也叫做哈希碰撞。是指当元素通过哈希函数运算后被映射到哈希表中同一位置的情况

哈希函数需要尽可能地保证计算简单并且散列地址分布均匀,而数组在内存中是一片连续的区域,所以哈希冲突是都无法避免的,那么哈希冲突的解决办法都有哪些?

前文提到了Set集合,Set集合底层对于哈希冲突的解决方案就是形成链,包括jdk1.8之后的红黑树,实际上也是对哈希冲突的一种解决办法。
解决哈希冲突问题的方法很多,下面给出一部分

  1. 开放地址法:当出现哈希冲突时,重新去寻找一个新的空闲哈希地址
    1. 线性探测法:哈希值不断加1,每次加1之后在进行哈希运算直到找到合适的位置,该方法只能单向寻找,性能稍差
    2. 平方探测法(二次探测):双向寻找位置的方法
  2. 链地址法:也是经常使用的一种方法,在发生哈希冲突的位置上形成链表,省时省力,但是如果成链元素过多就会大幅降低查询速度
  3. 再哈希法:同时构建多个哈希函数,如果第一个发生冲突就是用第二个.第三个…(上文图示的哈希算法就是典型的一种)
  4. 建立公共溢出区:将哈希表分为基本表和溢出表,发生冲突的元素存放在溢出表中

为什么重写equals就要重写hashCode方法

老生常谈的问题了属于是

首先在不重写的情况下我们默认调用的是Object类中的equals方法,如下:

public boolean equals(Object obj) {
        return (this == obj);
    }

hashCode方法是一个本地方法,每一个对象都有它的hashCode,而hashCode是我们向散列表中添加数据时判断是否重复的关键。

可以看出,默认情况下我们调用equals实际上比较的还是两个对象的地址值是否相同。

不重写的情况下:a.equals(b)如果==true,那么a和b一定是同一个对象,此时两者的hashcode也一定是相同的。

所以始终围绕一句话:equals为true的两个对象,hashCode一定相同
这也是我们重写这两个方法的核心规则

那么如果只重写了equals方法则可能会出现两个对象完全一致,但是hashCode不同的情况。此时如果我们使用散列集合去存储时就会出现问题,因为散列集合是使用hashCode来决定该元素的存储位置的,如果两个元素equals相同但hashcode不同就会产生两个完全相同的元素存储在散列表中的两个不同位置,如果我们要根据这个对象去获取数据时可能程序就会出现一些预料之外的错误

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

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

相关文章

【人工智能原理自学】一元一次函数感知器:如何描述直觉

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;笔记来自B站UP主Ele实验室的《小白也能听懂的人工智能原理》。 &#x1f514;本文讲解一元一次函数感知器&#xff1a;如何描述直觉&#xff0c;一起卷起来叭&#xff01; 目录…

基于Vue和SpringBoot的便利店仓库物资管理系统的设计与实现

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;Java全栈软件工程师一枚&#xff0c;来自浙江宁波&#xff0c;负责开发管理公司OA项目&#xff0c;专注软件前后端开发&#xff08;Vue、SpringBoot和微信小程序&#xff09;、系统定制、远程技术指导。CSDN学院、蓝桥云…

jvm内存模型简介

一、Jvm 的介绍 1、JVM体系结构 2、JVM运行时数据区 3、JVM内存模型 JVM运行时内存 共享内存区 线程内存区 3.1、共享内存区 共享内存区 持久带(方法区 其他) 堆(Old Space Young Space(den S0 S1)) 持久代&#xff1a; JVM用持久带&#xff08;Permanent Space&…

Java -- 软件开发整体流程;项目环境dev,test,staging,prod

软件开发整体介绍 作为一名软件开发工程师&#xff0c;我们需要了解在软件开发过程中的开发流程&#xff0c; 以及软件开发过程中涉及到的岗位角色&#xff0c;角色的分工、职责&#xff0c; 并了解软件开发中涉及到的四种软件环境。我们将从 软件开发流程、角色分工、软件环境…

7-4 乘法口诀数列

本题要求你从任意给定的两个 1 位数字 a1​ 和 a2​ 开始&#xff0c;用乘法口诀生成一个数列 {an​}&#xff0c;规则为从 a1​ 开始顺次进行&#xff0c;每次将当前数字与后面一个数字相乘&#xff0c;将结果贴在数列末尾。如果结果不是 1 位数&#xff0c;则其每一位都应成为…

1015:计算并联电阻的阻值(信奥赛一本通)

题目跳转&#xff1a;点击这里 时间限制: 1000 ms 内存限制: 65536 KB 【题目描述】 【输入】 两个电阻阻抗大小&#xff0c;浮点型&#xff0c;以一个空格分开。 【输出】 并联之后的阻抗大小&#xff0c;结果保留小数点后22位。 【输入样例】 1 2 【输出样例】 0.67 …

Windows下Cmake的简易工程构建

新建两个文件head,src&#xff0c;用于存放头文件和c文件。 再新建CMakeLists.txt文件&#xff0c;用于cmake配置。 当前文件结构: --->CMakeLists.txt | --->head | --->src新建一个头文件hello.h 内容如下: #ifndef HELLO_H #define HELLO_H #include "stdio…

Koxia and Number Theory(数论)

题目链接&#xff1a; Problem - C - Codeforces 题目大意&#xff1a; 给定一个数组a.问是否存在x,使得gcd(aix,ajx)1 对任意(1<x<j<n)成立 思路&#xff1a; 首先不难发现&#xff0c;数组不可以出现相同的数字 记biaix 要满足gcd(bi,bj)1 对任意(1<x<…

Python数据分析案例17——电影人气预测(特征工程构建)

案例背景 本次案例是中国人民大学“人工智能与机器学习&#xff08;2022年秋季&#xff09;”课程的课堂竞赛。 比赛是根据有关电影的各种信息来预测电影的受欢迎程度&#xff0c;包括演员、工作人员、情节关键字、预算、收入、海报、上映日期、语言、制作公司、国家、TMDB 投…

【概率论】期末复习笔记:参数估计

参数估计目录一、点估计1. 估计量的概念2. 估计量的求法矩估计法最大似然估计法二、估计量的评选标准1. 无偏性2. 有效性3. 相合性总结三、区间估计1. 双侧区间估计2. 单侧区间估计四、正态总体参数的区间估计σ2\sigma^2σ2已知&#xff0c;考察μ\muμ</font>σ2\sigma…

车载以太网 - DoIP报文类型 - 02

上次我们聊了什么是DoIP&#xff0c;以及DoIP在车载网络以及车载ECU中的作用&#xff0c;我们应该有大概的了解&#xff0c;以及它的极大地作用&#xff0c;今天我们开始全面的去了解它&#xff0c;毕竟只有等我们了解它以后&#xff0c;才能更好的应用。今天要聊的第一个内容呢…

沃太能源冲刺上市:亿纬锂能、高瓴均为股东,收入主要来自境外

12月30日&#xff0c;沃太能源股份有限公司&#xff08;下称“沃太能源”&#xff09;在上海证券交易所递交招股书&#xff0c;准备在科创板上市。本次冲刺科创板上市&#xff0c;沃太能源计划募资10亿元&#xff0c;中信证券为其保荐机构。 按照计划&#xff0c;沃太能源将其中…

网络编程 udp/ip协议 c/s模型

目录 1.概念​编辑 2.代码解析 1.recvfrom函数 2.sendto函数 3.内核泄露问题 整体代码 1.概念 2.代码解析 1.recvfrom函数 该函数接收数据报&#xff0c;并存储源地址&#xff0c;即得到当前服务器接收到的消息&#xff0c;并且存储在参数2&#xff0c;该函数是阻塞的&#x…

c++构造和析构

1.构造函数 1.构造函数特性 构造函数名字和类名相同构造函数没有返回值(void有返回值&#xff0c;返回值为空)不写构造函数&#xff0c;每一个类中都存在默认的构造函数&#xff0c;默认的构造函数是没有参数的default显示使用默认的构造函数delete删掉默认函数当我们自己写了…

MM采购订单及发票相关后台表介绍(图解)

EKPO 采购凭证项目 EKKO 采购凭证抬头 EORD 采购货源清单 EINA 采购信息记录 - 一般数据 EINE 采购信息记录 - 采购组织数据 EKET 计划协议计划行 EKES 供应商确认 EKKN 采购凭证中的帐户设置 EKBE 采购凭证历史 EKBZ 每个采购凭证的历史&#xff1a;交货费用 RBKP 凭…

在wsl下开发T113的主线linux(2)-编译awboot

意外发现有awboot能够代替uboot直接引导内核&#xff0c;体验了一下果断选择awboot&#xff0c;因为足够简洁&#xff0c;编译大小只有32k&#xff0c;和uboot接近1M的体量相比&#xff0c;简直是小而美&#xff0c;启动速度也比uboot快上不少&#xff0c;也能同时支持sd卡&…

DoIP协议从入门到精通系列——车载网络拓扑

因特网协议(IP-Internet protocol)是互联网规范中的基本协议,它仅是支持互联网正常运转“TCP/IP”协议簇之一。UDP协议也是TCP/IP协议体系中的内容(因为名称中只含有TCP/IP名称,往往会忽略UDP)。以太网引入到车载网络后,汽车也会慢慢进入车联网时代(或者物联网,万物互…

aws codepipeline 在pipeline构建过程中使用变量

参考资料 Action structure reference codebuild构建环境中的环境变量 codepipeline中的变量 在codePipeline中使用变量 对于codepipeline来说&#xff0c;管道结构中的每个操作都有自身的结构和定义&#xff0c;本文主要讨论不同资源的输出变量。 基本概念 变量允许用户…

数据完整性(一)

目录 数据完整性&#xff1a; 什么是数据完整性&#xff1a; 数据完整性的类型 1&#xff1a;实体完整性 2&#xff1a;域完整性&#xff1a; 3、引用完整性&#xff1a; 4、自定义完整性&#xff1a; 完整性约束&#xff1a; 数据完整性的实现方式&#xff1a; 实体完整性&a…

抽象⼯⼚模式

抽象⼯⼚模式 1.抽象工厂模式介绍 抽象⼯⼚模式与⼯⼚⽅法模式虽然主要意图都是为了解决&#xff0c;接⼝选择问题。但在实现上&#xff0c;抽象⼯⼚是⼀ 个中⼼⼯⼚&#xff0c;创建其他⼯⼚的模式。 2.案例场景模拟 2.1场景简述 很多时候初期业务的蛮荒发展&#xff0c;也…