Java实现哈希表

1.哈希表定义

        哈希表(hash table,也叫散列表),是根据关键码值(key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫散列函数,存放记录的数组叫散列表。

哈希表可以提供快速的插入和查找工作,哈希表运算的非常快,而且编程实现也比较容易。哈希表是数组和链表结构。 

2.哈希表的原理 

        1.哈希表是链表和数组实现的(数组里面存储的元素是链表的头结点)

 

        2.哈希表是环形数组实现的

        什么是环形数组呢?

就是近似于环,数组的最大索引后面就是0索引,这种思想主要是通过取模计算实现的,

主要公式:  index = index % arr.length

就按照上图举例吧: 例如 计算出hash值为 10   10 % 8 = 2 所以应该存放 2 索引的位置

        3.为什么Jdk会在链表长度超过8时转换为红黑树?

正常数据情况下,就算数据量比较大,也不会出现超过8的情况!

但是为什么会用这种机制呢?

这种机制呢,主要是为了防范于未然,防止被攻击,有些人专门造一些攻击的Hash数据(这些hash值都会相互之间冲突),就会形成一个非常长的链表,会使你整个服务器下降,

主要目的:为了防止被恶意攻击

        4.为什么Jdk的底层数组长度都是2^n?

.位运算符(&)比模运算(%),效率高,这样做可以提高效率。

        它对应的二进制表示中只有一位是1,其余位都是0。在这种情况下,计算哈希值h与哈希表长度m的取模运算等价于对2^n取模,可以使用位运算(&)实现,即 h & (m - 1)。

所以长度为2 ^ n就会有一个规律:   [hash & (数组长度-1)] 等价于 [hash % 数组长度]

这样的做法的好处是:当哈希表长度是2^n时,可以用位运算来代替较慢的取模运算,从而提高哈希表的性能。并且由于2^n的二进制表示中只有一位是1,因此在使用位运算计算哈希表索引时,可以保证结果均匀分布,减少哈希碰撞的概率。

需要注意的是,即使哈希表的长度不是2^n,仍然可以使用位运算来代替取模运算。但是,这样做会使得位运算的效率降低,并且不能保证哈希值均匀分布,容易导致哈希冲突,影响哈希表的性能。

 3.代码原理分析

3.1添加

1.hash表在添加的使用key.hashCode()计算出的键的hash值

2.再利用位运算符&数组长度 - 1(% 数组长度)计算出应该要存的下标

3.接下俩就是数组中有元素吗? 

        3.1 没有则直接存

        3.2 有的话,一直往后找,找到最后一个元素,添加在它的尾部

3.2删除

删除的原理:主要思想就是找到删除元素上一个节点(如果是头部,则头部下一个节点为空)

1.根据要删除的元素,计算其hash值,并找到对应的数组索引。

2.在该索引位置查找元素,如果存在,则执行删除操作。

3.删除元素后,根据具体情况可能需要进行以下操作:

        3.1 如果删除后该位置没有其他元素,则直接将该位置设为null,表示该位置为空。

        3.2如果删除后该位置有其他元素(可能是发生了哈希冲突),则可能需要进行链表或其他数据结构的调整,以保持哈希表的正确性。

具体删除时的操作流程可能因不同的哈希表实现而有所不同。例如,对于开放寻址法的哈希表实现,在删除元素时可能会使用查找下一个空槽的方式来处理哈希冲突。而对于拉链法的哈希表实现,在删除元素时可能需要遍历链表结构并进行节点的删除操作。

需要注意的是,哈希表的删除操作可能会导致哈希表的负载因子过低或链表过长等问题,影响哈希表的性能。为了保持哈希表的高效性,可能需要根据具体情况进行动态缩容、重新哈希或其他优化操作。

3.3扩容

哈希表的扩容是为了保持哈希表的负载因子,在一个可接受的范围内,从而保持哈希表的性能稳定。

当哈希表中的元素数量达到一定阈值(0.75)时,就会触发扩容操作。具体的扩容原理如下:

1.创建一个新的更大的数组,通常将数组长度扩大为原来的两倍。

2.遍历原来的哈希表,将每个元素重新计算哈希值,并放入新的数组中的对应位置

3.这个过程被称为重新哈希(Rehashing),它会根据新数组大小重新计算元素的存储位置,以确保元素在新数组中的分布更为均匀,减少哈希冲突。

4.将新的数组设置为哈希表的底层数组,用于存储元素。

4.代码实现

注意:这里我怕不好理解我没有用位运算符(&)全部用了%

4.1准备工作

定义节点类,和定义成员变量

// 哈希表的代码实现
public class HashTable<K,V>{// 定义哈希表节点class HashNode<K, V>{private final K key;//键private V value;//值private HashNode<K, V> next;//下一个节点的引用public HashNode(K key, V value) {this.key = key;this.value = value;this.next = null;}public K getKey() {return key;}public V getValue() {return value;}public void setValue(V value) {this.value = value;}public HashNode<K, V> getNext() {return next;}public void setNext(HashNode<K, V> next) {this.next = next;}}private int SIZE;//定义数组的长度private final double LOAD_FACTOR = 0.75; // 负载因子,用于决定何时进行扩容private int size; // 哈希表中节点的数量private HashNode<K,V>[] table;//定义一个存链表的数组public HashTable(int initialCapacity) {//初始化数组this.SIZE = initialCapacity;table = new HashNode[SIZE];this.size = 0;//初始化节点数量为0}}

4.2计算键的应该存入的下标

 private int getHash(K key) {//利用jdk的哈希算法来产生一个随机数return Math.abs(key.hashCode() % SIZE);//获得键的hash值应放入的索引,取绝对值以保证正数}

4.3扩容

//扩容private void resize() {int newSize = SIZE * 2; // 新的哈希表大小为原来的两倍HashNode<K, V>[] newTable = new HashNode[newSize]; // 创建新的数组for (int i = 0; i < SIZE; i++) {HashNode<K, V> current = table[i]; // 获取原数组的每个链表的头节点while (current != null) {K key = current.getKey();V value = current.getValue();// 根据新的数组大小重新计算哈希值int hash = Math.abs(key.hashCode() % newSize);// 插入节点到新的数组中if (newTable[hash] == null) {//头结点newTable[hash] = new HashNode<>(key, value);} else {//非头结点HashNode<K, V> newNode = newTable[hash];while (newNode.getNext() != null) {//找到next不为null的节点进行存储newNode = newNode.getNext();}newNode.setNext(new HashNode<>(key, value));}current = current.getNext(); // 继续遍历原数组的下一个节点}}table = newTable; // 将引用指向新的数组SIZE = newSize; // 更新数组大小}

4.4添加

public void put(K key,V value){//向hash表中插入键值对int hash = getHash(key);//计算键的hash值放入的索引if (table[hash] == null){ //为空table[hash] = new HashNode<>(key,value);//直接插入节点}else {//不为空HashNode<K, V> current = table[hash];while (current.getNext() != null) { // 遍历链表,直到找到最后一个节点if (current.getKey().equals(key)) { // 如果找到键相同的节点current.setValue(value); // 更新值return;}current = current.next;}//找不到就判断最后一个是不是if (current.getKey().equals(key)) { // 如果最后一个节点的键与目标键相同current.setValue(value); // 更新值} else { // 如果最后一个节点的键与目标键不同current.setNext(new HashNode<>(key, value)); // 插入新节点作为最后一个节点的下一个节点}}// 检查负载因子是否超过阈值,如果超过则进行扩容if ((double) size / SIZE > LOAD_FACTOR) {resize();}}

4.5删除

public void remove(K key) { // 移除指定键的节点int hash = getHash(key); // 计算键的哈希值HashNode<K, V> previous = null; // 记录前一个节点HashNode<K, V> current = table[hash]; // 获取对应索引位置的节点while (current != null) { // 遍历链表if (current.getKey().equals(key)) { // 如果找到键相同的节点if (previous == null) { // 如果当前节点为链表的第一个节点table[hash] = current.getNext(); // 将下一个节点设为新的头节点} else { // 如果当前节点不是链表的第一个节点previous.setNext(current.getNext()); // 将上一个节点的next指向当前节点的下一个节点,跳过当前节点}return;}previous = current; // 更新前一个节点current = current.getNext(); // 继续遍历下一个节点}}

4.5判空

public boolean isEmpty() { // 判断哈希表是否为空for (int i = 0; i < SIZE; i++) { // 遍历数组if (table[i] != null) { // 如果存在非空的节点return false; // 表示哈希表不为空}}return true; // 如果数组中所有元素都为空,则返回true表示哈希表为空}

4.6获得键对应的值

 public V get(K key) { // 根据键获取值int hash = getHash(key);HashNode<K,V> current = table[hash];//获取对应索引的位置while (current != null) { // 遍历链表if (current.getKey().equals(key)) { // 如果找到键相同的节点return current.getValue(); // 返回对应的值}current = current.getNext(); // 继续遍历下一个节点}return null; // 如果未找到对应的键,则返回null}

4.7获得节点的个数

   public int size() { // 获取哈希表中节点的数量int count = 0; // 计数器for (int i = 0; i < SIZE; i++) { // 遍历数组HashNode<K, V> current = table[i]; // 获取对应索引位置的节点while (current != null) { // 遍历链表count++; // 计数器加一current = current.getNext(); // 继续遍历下一个节点}}return count; // 返回计数器的值,即哈希表中节点的数量}

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

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

相关文章

浏览器唤起钉钉 各项功能

浏览器唤起钉钉对应人员聊天 文档地址 https://open.dingtalk.com/document/client/unified-routing-protocol 唤起聊天 不过只能唤起叮叮的名片 id为叮叮号 <a href"dingtalk://dingtalkclient/action/sendmsg?dingtalk_id{id}"></a>id&#xff1a; …

maven的pom.xml文件显示被删除

文章目录 1.问题情况2.问题分析3.问题解决 1.问题情况 2.问题分析 这些 pom.xml 文件被 maven 视为了忽略文件。 3.问题解决 路径&#xff1a;File --> Settings --> Build&#xff0c;Execution&#xff0c;Deployment --> Build Tools --> Maven --> Ignor…

06_Node.js服务器开发

1 服务器开发的基本概念 1.1 为什么学习服务器开发 Node.js开发属于服务器开发&#xff0c;那么作为一名前端工程师为什么需要学习服务器开发呢&#xff1f; 为什么学习服务器开发&#xff1f; 能够和后端程序员更加紧密配合网站业务逻辑前置扩宽知识视野 1.2 服务器开发可…

bochs 对 Linux0.11 进行调试 (TODO: 后面可以考虑集成 vscode+gdb+qemu)

我在阅读 Linux0.11 源码时&#xff0c;对一个指令 LDS 感到困惑。 看了下 intel 指令集手册&#xff0c;能猜到 LDS 的功能&#xff0c;但不确定。 于是决定搭建调试环境&#xff0c;看看 LDS 的功能是否真如自己猜测。 首先 make debug 运行 qemu-Linux0.11&#xff0c;命…

基于Java+SpringBoot+Vue在线家具商城系统的设计与实现 前后端分离【Java毕业设计·文档报告·代码讲解·安装调试】

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

Ubuntu20.04 配置 yolov5_ros 功能包记录

文章目录 本文参考自博主源801,结合自己踩坑后修改 项目地址:https://github.com/mats-robotics/yolov5_ros 1.新建工作空间 新建一个工作空间 yolo_ros(名字可自定义),在 yolo_ros 下新建文件夹 src 并catkin_make进行编译 2. 安装相机驱动,可以选用较为主流的 usb_cam 或…

ios safari 浏览器跳转页面没有自适应

今天开发遇到了一个问题&#xff0c;当用户点击浏览器中的表单进行注册时&#xff0c;表单元素会放大&#xff0c;随后跳转页面无法还原到初始状态。 这是因为如果 的 font-size 被设定为 16px 或更大&#xff0c;那么 iOS 上的 Safari 将正常聚焦到输入表单中。但是&#xff…

2023版 STM32实战8 独立看门狗(IWDG)

IWDG简介 STM32F10xxx内置两个看门狗&#xff0c;提供了更高的安全性、时间的精确性和使用的灵活性。两个看门狗设备(独立看门狗和窗口看门狗)可用来检测和解决由软件错误引起的故障。 说人话就是能解决程序跑飞的问题。 编写代码思路 -1- 使用这个功能必须解除写保护 -2-…

FLIP动画做拖拽排序效果

先来看效果 index.html文件 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content&quo…

Netty深入浅出(无处不在的IO)

为什么要有Netty Netty是为了解决网络编程的复杂性和提供易于使用、高性能和可扩展的框架而开发的。它通过提供一组可重用的组件来处理网络通信的低级细节&#xff0c;例如套接字管理、线程和缓冲&#xff0c;简化了开发网络应用程序的过程。这使开发人员可以专注于应用程序逻…

自然语言处理 | WordNet

WordNet是词汇数据库,即英语词典,专为自然语言处理而设计。 Synset是一种特殊的简单接口,存在于 NLTK 中, 用于在 WordNet 中查找单词。同义词集实例是表达相同概念的同义词的分组。有些单词只有一个同义词集,有些则有多个。

C#对字典容器Dictionary<TKey, TValue>内容进行XML序列化或反序列化报错解决方法

一、问题描述 在使用C#对字典容器Dictionary<TKey, TValue>内容进行XML序列化报错【System.Exception:“不支持类型 System.Collections.Generic.Dictionary2[[System.String, mscorlib, Version2.0.0.0, Cultureneutral, PublicKeyTokenb77a5c561934e089],[System.Strin…

基于matlab统计Excel文件一列数据中每个数字出现的频次和频率

一、需求描述 如上表所示&#xff0c;在excel文件中&#xff0c;有一列数&#xff0c;统计出该列数中&#xff0c;每个数出现的次数和频率。最后&#xff0c;将统计结果输出到新的excel文件中。 二、程序讲解 第一步&#xff1a;选择excel文件&#xff1b; [Filename, Pathn…

C++笔记之信号量、互斥量与PV操作

C笔记之信号量、互斥量与PV操作 文章目录 C笔记之信号量、互斥量与PV操作1.信号量概念2.信号量例程一3.信号量例程二4.信号量例程三5.互斥量6.PV操作概念7.PV操作详解——抄自&#xff1a;https://mp.weixin.qq.com/s/vvjhbzsWQNRkU7-b_dURlQ8.PV操作的英文全称 1.信号量概念 …

HTTPS建立连接的过程

HTTPS 协议是基于 TCP 协议的&#xff0c;因而要先建立 TCP 的连接。在这个例子中&#xff0c;TCP 的连接是在手机上的 App 和负载均衡器 SLB 之间的。 尽管中间要经过很多的路由器和交换机&#xff0c;但是 TCP 的连接是端到端的。TCP 这一层和更上层的 HTTPS 无法看到中间的包…

拼多多API接口的使用方针如下:

了解拼多多API接口 拼多多API接口是拼多多网提供的一种应用程序接口&#xff0c;允许开发者通过程序访问拼多多网站的数据和功能。通过拼多多API接口&#xff0c;开发者可以开发各种应用程序&#xff0c;如店铺管理工具、数据分析工具、购物比价工具等。在本章中&#xff0c;我…

Spring中的设计模式

目录 工厂模式 组合模式 适配器模式 代理模式 单例模式 观察者模式 模板方法模式 责任链模式 Spring有着非常优雅的设计&#xff0c;很多地方都遵循SOLID原则&#xff0c;里面的设计模式更是数不胜数大概有以下几种&#xff1a; 工厂模式 所谓的工厂模式&#xff0c;核…

在MySQL中使用!=还能走索引吗?

在MySQL中使用!还能走索引吗&#xff1f; 一般情况下&#xff0c;我们会在一个索引上较多的使用等值查询或者范围查询&#xff0c;此时索引大多可以帮助我们极快的查询出我们需要的数据。 那当我们在where条件中对索引列使用!查询&#xff0c;索引还能发挥他的作用吗&#xf…

vue3 中使用 echarts 图表——准备篇

我们常常在项目中使用图表来表示数据&#xff0c;而目前最常用的图标就是echarts&#xff0c;接下来我们就开始学习在vue中使用echarts图标。 一、准备一个vue项目&#xff08;一般通过vite来构建&#xff0c;而不是vue-cli&#xff09; 1.找到打开vite官网 2. 运行创建命令 …

英语字典的一些 关键字 解释:

1. specialized &#xff1a;专业术语 这里的specialized 后面还接了 finance & economics 含义是 在经济和金融的领域&#xff0c;principal 作为一个专业术语&#xff0c;含义是 “本金&#xff0c;可生息资本” 2. XXXXXX