哈希表(HashMap、HashSet)

文章目录

  • 一、 什么是哈希表
  • 二、 哈希冲突
    • 2.1 为什么会出现冲突
    • 2.2 如何避免出现冲突
    • 2.3 出现冲突如何解决
  • 三、模拟实现哈希桶/开散列(整型数据)
    • 3.1 结构
    • 3.2 插入元素
    • 3.3 获取元素
  • 四、模拟实现哈希桶/开散列(泛型)
    • 4.1 结构
    • 4.2 插入元素
    • 4.3 获取元素
  • 五、区别
    • 5.1 TreeMap 和 HashMap 的区别
    • 5.2 TreeSet 和 HashSet 的区别
  • 六、HashMap 源码分析
    • 6.1 成员变量+结点定义
    • 6.2 构造方法
    • 6.3 put()

一、 什么是哈希表

  1. 是个存储结构:可以让我们一次从表中直接拿到想要的元素,时间复杂度为O(1)
  2. 为什么能实现O(1):通过哈希(散列)方法,使元素的存储位置和它的关键码之间建立一一映射的关系
    • 如果想要存取元素,都是利用哈希(散列)方法 + 关键码,从而计算出index位置,然后进行操作(怎么放的就怎么给它取出来
    • 哈希函数示例:hash(key) = key % capacity

二、 哈希冲突

2.1 为什么会出现冲突

  1. 原因:两个不一样的关键字通过相同的哈希函数映射到了相同的位置
    • 两个不一样的假如哈希函数是【hash(key) = key % capacity】,如果有两个key,分别为4和14,capacity为10,此时4和14生成的位置都是一样的

2.2 如何避免出现冲突

  1. 哈希冲突无法规避:由于哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,所以冲突的发生是必然的,我们能做的只是尽量降低冲突率
  2. 方式
    • 方式一:将哈希函数设置地更为合理。不过一般Java库已经帮我们写好了哈希方法,不需要程序员去设计
      • 哈希函数设计原则:【如果有m个元素,哈希出来的地址一定在0 ~ m-1】 + 【元素能够均匀地分布在整个空间里】 + 【简单】
      • 直接定制法:取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
      • 除留余数法
        • Hash(key) = key% p(p<=capacity),p是个最接近或者等于capacity的p
      • 平方取中法
      • 折叠法
      • 随机数法
      • 数学分析法
    • 方式二:调节负载因子
      在这里插入图片描述

2.3 出现冲突如何解决

  1. 解决方法一:闭散列(将key存到哈希冲突位置的其他空位置去)
    • 寻找空位置方法
      • 线性探测:找到下一个空的位置,然后把冲突的key放进去。但这样会把冲突的元素都挤在一起
      • 二次探测
        在这里插入图片描述
    • 闭散列缺陷
      • 数组利用率/空间利用率不高:利用率高的情况是把同样下标的放在一起,不占用其他格子
      • 不方便删除:假如4和14都在同一个下标,14放在了其他位置,但我们定义出来是在4下标,此时不好删除
  2. 解决方法二:开散列/哈希桶
    • 关于O(1)时间的复杂度
      • 虽然哈希表一直在强调哈希冲突,但其实实际中我们认为哈希表的冲突率是不高的,即每个桶中的链表长度是一个常熟。所以我们通常认为哈希表的插入/删除/查找的时间复杂度为O(1)

在这里插入图片描述

三、模拟实现哈希桶/开散列(整型数据)

3.1 结构

public class HashBucket {//Node相当于Entrystatic class Node {private int key;private int value;private Node next;public Node(int key, int value) {this.key = key;this.value = value;}}public Node[] array;public int usedSize;//默认的负载因子为0.75private static final float DEFAULT_LOAD_FACTOR = 0.75f;public HashBucket() {this.array = new Node[10];}
}

3.2 插入元素

  1. 思路
    • 首先根据key和哈希函数计算出对应的index,由于该index下包含了冲突的元素,所以我们需要遍历该链表
      • 重复的值需要更新:注意,因为HashMap是继承了Map接口,而Map的一大特点就是【如果有相同的key,会更新Value值】,所以如果有相同的我们需要更新
    • 如果遍历完发现没有重复的,就进行插入,可以头插也可以尾插,此处我们用的是尾插
    • 插入完毕后,需要计算负载因子,如果负载因子大于定义的值,就需要扩容
      • 扩容需要注意的问题:需要把桶中的数据一个个拿出来重新哈希到新的数组中。因为扩容后,原本的key哈希后得到的index很可能不是原来的index了,所以需要重新哈希。
public void put(int key,int val) {Node node = new Node(key,val);int index = key % array.length;//遍历index位置下方的链表Node cur = array[index];while (cur != null) {if(cur.key == key) {cur.value = val;return;}cur = cur.next;}//头插node.next = array[index];array[index] = node;usedSize++;//计算负载因子if(loadFactor() >= DEFAULT_LOAD_FACTOR) {//扩容resize();}
}//重新哈希原来的数据 !!!
private void resize() {//2倍扩容Node[] tmpArray = new Node[array.length * 2];//遍历原来的数组下标的每个链表for (int i = 0; i < array.length; i++) {Node cur = array[i];while (cur != null) {Node curNext = cur.next;//需要记录下来 原来链表的下一个节点的位置int index = cur.key % tmpArray.length;//新数组的位置//采用头插法 放到新数组的index位置cur.next = tmpArray[index];//这里修改之后 cur的next已经变了tmpArray[index] = cur;cur = curNext;}}array = tmpArray;
}//计算负载因子
private float loadFactor() {return usedSize*1.0f / array.length;
}

3.3 获取元素

在这里插入图片描述

public int get(int key) {int index = key % array.length;Node cur = array[index];//变成红黑树的过程太复杂,此处不模拟while (cur != null) {if(cur.key == key) {return cur.value;}cur = cur.next;}return -1;
}

四、模拟实现哈希桶/开散列(泛型)

4.1 结构

public class HashBucket<K,V> {static class Node<K,V> {private K key;private V value;private Node<K,V> next;public Node<K,V>(K key, V value) {this.key = key;this.value = value;}}public Node<K,V>[] array;public int usedSize;//默认的负载因子为0.75private static final float DEFAULT_LOAD_FACTOR = 0.75f;public HashBucket() {this.array = (Node<K,V>[])new Node[10];}
}

4.2 插入元素

public void put(K key,V val) {Node<K,V> node = new Node<>(key,val);int hash = key.hashCode();//控制成合理的位置,因为hashCode生成的数字一般都挺大,所以需要%int index = hash % array.length;Node<K,V> cur = array[index];while (cur != null) {if(cur.key.equals(key)) {cur.val = val; //如果val一样就更新return;}cur = cur.next;}node.next = array[index];array[index] = node;usedSize++;//计算负载因子
}

4.3 获取元素

  1. 代码解析
    • 自定义类型需要重写 equals 和 hashCode方法,hashCode用来找index位置,equals用来判断元素是否相同
class Person {public String id;public Person(String id) {this.id = id;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return Objects.equals(id, person.id);}public int hashCode() {return Objects.hash(id);}
}
public V get(K key) {int hash = key.hashCode();int index = hash % array.length;//控制成合理的位置Node<K,V> cur = array[index];while (cur != null) {if(cur.key.equals(key)) {return cur.val;}cur = cur.next;}return null;
}
  1. 测试:因为此时person1和person2的hashCode结果是一样的,所以最后能打印出的name是【zhangsan】,即可以用person2去找到person1
public static void main(String[] args) {Person person1 = new Person("1234");Person person2 = new Person("1234");HashBuck<Person,String> hashBucket = new HashBucket<>();hashBuck.put(person1,"zhangsan");String name = hashBuck.get(person1);System.out.println(name); }

五、区别

5.1 TreeMap 和 HashMap 的区别

Map底层结构TreeMapHashMap
底层结构红黑树哈希桶
插入/删除/查找时间复杂度O(logN)O(1)
是否有序关于Key有序无序
线程安全不安全不安全
插入/删除/查找区别需要进行元素比较通过哈希函数计算哈希地址
比较与覆写key必须能够比较,否则会抛出 ClassCastException异常自定义类型需要覆写equals和 hashCode方法
应用场景需要Key有序场景下Key是否有序不关心,需要更高的时间性能

5.2 TreeSet 和 HashSet 的区别

Map底层结构TreeMapHashMap
底层结构红黑树哈希桶
插入/删除/查找时间复杂度O(logN)O(1)
是否有序关于Key有序无序
线程安全不安全不安全
插入/删除/查找区别按照红黑树的特性来进行插入和删除 先计算key哈希地址,然后进行插入和删除
比较与覆写key必须能够比较,否则会抛出 ClassCastException异常自定义类型需要覆写equals和 hashCode方法
应用场景需要Key有序场景下Key是否有序不关心,需要更高的时间性能

六、HashMap 源码分析

6.1 成员变量+结点定义

在这里插入图片描述

6.2 构造方法

  1. Map<String,Intger> map1 = new HashMap<>(1000):此时写着容量是1000,但实际上是2次幂数,容量为1024
    在这里插入图片描述

6.3 put()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

QSqlDatabase在多线程中的使用

Qt中多线程使用数据库_qt数据库管理类支持多数据库,多线程-CSDN博客 1. 代码&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include <QSqlDatabase> #include <QSqlQuery> #include <QSqlError>…

Python编码系列—Python备忘录模式:掌握对象状态保存与恢复技术

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

【JavaSE】抽象类

1. 抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 在打印图形例子中, 我们…

婚恋交友小程序的设计思路与用户体验优化

在数字化时代&#xff0c;婚恋小程序作为一种新兴的婚恋交友平台&#xff0c;正逐渐成为单身人士寻找伴侣的重要工具。一个优秀的婚恋小程序不仅要有创新的设计思路&#xff0c;还要注重用户体验的优化。编辑h17711347205以下是婚恋小程序的设计思路与用户体验优化的详细阐述&a…

服务器数据恢复—存储映射到服务器上的卷无法挂载的数据恢复案例

服务器存储数据恢复环境&故障&#xff1a; 一台存储上有一组由16块FC硬盘组建了一组raid。存储前面板上的对应10号和13号硬盘的故障灯亮起&#xff0c;存储映射到redhat linux操作系统服务器上的卷挂载不上&#xff0c;业务中断。 服务器存储数据恢复过程&#xff1a; 1、…

【AIGC】ChatGPT提示词解析:如何打造个人IP、CSDN爆款技术文案与高效教案设计

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;打造个人IP爆款文案提示词使用方法 &#x1f4af;CSDN爆款技术文案提示词使用方法 &#x1f4af;高效教案设计提示词使用方法 &#x1f4af;小结 &#x1f4af;前言 在这…

迎国庆,开源完全免费工作流引擎AntFlow 0.9最强版本发布,支持tidb,提升易用性and more...

AntFlow是一款前端仿钉钉的企业级工作流引擎。后端既可嵌入到现有业务系统&#xff0c;也可以做为独立的流程引擎中台部署&#xff08;SAAS模式&#xff09;。嵌入业务系统模式已经在笔者所在企业使用多年&#xff0c;功能丰富&#xff0c;能适多种国产办公场景&#xff1b;简单…

从密码学看盲拍合约:智能合约的隐私与安全新革命!

文章目录 前言一、什么是盲拍合约&#xff1f;二、盲拍合约的优势1.时间压力的缓解2.绑定与秘密的挑战 三、盲拍合约的工作原理1.提交盲出价2.披露出价3.结束拍卖4.退款机制 四、代码示例总结 前言 随着区块链技术的发展&#xff0c;智能合约在各种场景中的应用越来越广泛。盲…

芝法酱学习笔记(0.5)——使用jenkins做自动打包

前言 上节讲了SpringBoot上的打包。但这些过程都是手动的&#xff0c;在实际的开发测试时&#xff0c;自动化的打包部署&#xff0c;可以大大提升团队开发的效率 一、去官网下载 1.1 官网安装命令 对于如何安装的问题&#xff0c;我向来推荐官网 wget -O /usr/share/keyri…

针对考研的C语言学习(定制化快速掌握重点2)

1.C语言中字符与字符串的比较方法 在C语言中&#xff0c;单字符可以用进行比较也可以用 > , < ,但是字符串却不能用直接比较&#xff0c;需要用strcmp函数。 strcmp 函数的原型定义在 <string.h> 头文件中&#xff0c;其定义如下&#xff1a; int strcmp(const …

ubuntu server 常用配置

这里写目录标题 0001 虚拟机静态IP0002 vim tab 4个空格0003 设置时区0004 网络端口查看端口开放端口 0005 修噶机主机名 0001 虚拟机静态IP win网络链接&#xff0c;IP地址&#xff1a;192.168.220.1 - NAT网关&#xff1a;192.168.220.2 - ubuntu静态IP设置&#xff1a; ca…

前端——Ajax和jQuery

一、Ajax Ajax即“Asynchronous Javascript And XML”&#xff08;异步 JavaScript 和 XML&#xff09;&#xff0c; 通过 JS 异步的向服务器发送请 求并接收响应数据。 同步访问&#xff1a;当客户端向服务器发送请求时&#xff0c;服务器在处理的过程中&#xff0c;浏览器…

【韩顺平Java笔记】第5章:程序控制结构

文章目录 102. 回顾上一章节103. 顺序控制103.1 顺序控制 104. 单分支使用104.1 分支控制 if-else 介绍104.2 单分支 105. 单分支流程图106. 双分支使用107. 双分支流程图108. 双分支练习题109. 多分支使用109.1 多分支的流程图 110. 多分支练习1111. 多分支练习2112. 嵌套分支…

最大正方形 Python题解

最大正方形 题目描述 在一个 n m n\times m nm 的只包含 0 0 0 和 1 1 1 的矩阵里找出一个不包含 0 0 0 的最大正方形&#xff0c;输出边长。 输入格式 输入文件第一行为两个整数 n , m ( 1 ≤ n , m ≤ 100 ) n,m(1\leq n,m\leq 100) n,m(1≤n,m≤100)&#xff0c;接…

ubuntu 开启root

sudo passwd root#输入以下命令来给root账户设置密码 sudo passwd -u root#启用root账户 su - root#要登录root账户 root 开启远程访问&#xff1a; 小心不要改到这里了&#xff1a;sudo nano /etc/ssh/ssh_config 而是&#xff1a;/etc/ssh/sshd_config sudo nano /etc/ssh…

828华为云征文|部署去中心化网络的 AI 照片管理应用 PhotoPrism

828华为云征文&#xff5c;部署去中心化网络的 AI 照片管理应用 PhotoPrism 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 PhotoPrism3.1 PhotoPrism 介绍3.2 PhotoPrism…

【Redis】如何在 Ubuntu 上安装 Redis 5

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 本期内容主要介绍如何在 Ubuntu 上安装 Redis5 一些碎碎念&#xff1a; 本来这期内容介绍如何在 Centos 安装 Redis …

基于ScriptableObject设计游戏数据表

前言 本篇文章是针对之前对于ScriptableObject概念讲解的实际应用之一&#xff0c;在游戏开发中&#xff0c;我们可以使用该类来设计编辑器时的可读写数据表或者运行时的只读数据表。本文将针对运行时的只读数据表的应用进行探索&#xff0c;并且结合自定义的本地持久化存储方式…

cheese安卓版纯本地离线文字识别插件

目的 cheese自动化平台是一款可以模拟鼠标和键盘操作的自动化工具。它可以帮助用户自动完成一些重复的、繁琐的任务&#xff0c;节省大量人工操作的时间。可以采用Vscode、IDEA编写&#xff0c;支持Java、Python、nodejs、GO、Rust、Lua。cheese也包含图色功能&#xff0c;识别…

SpringBoot——基础配置

但是还需要删除pom.xml中的标签——模板的文件也同样操作 banner的选项——关闭 控制台 日志 banner图片的位置——还会分辨颜色 在 Java 的日志框架&#xff08;如 Logback、Log4j2 等&#xff09;中&#xff0c;logging.level.root主要用于设置根日志记录器的日志级别…