哈希表(HashMap、HashSet)

news2024/11/17 6:53:33

文章目录

  • 一、 什么是哈希表
  • 二、 哈希冲突
    • 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相当于Entry
    static 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.75
    private 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.75
    private 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;
    }

    @Override
    public 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.coloradmin.cn/o/2175240.html

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

相关文章

javaweb之会话管理

Cookie&#xff1a; 1. Cookie 的定义 Cookie 是存储在用户浏览器中的小块数据&#xff0c;通常由服务器发送并存储&#xff0c;以便在用户浏览器和服务器之间保持会话状态。每次用户发送请求时&#xff0c;浏览器都会自动附带相应的 Cookie&#xff0c;允许服务器辨识用户。…

58 深层循环神经网络_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录深度循环神经网络1. 模型复杂性增加2. 训练数据不足3. 梯度消失和爆炸4. 正则化不足5. 特征冗余总结 函数依赖关系简洁实现训练与预测小结练习 深度循环神经网络 &#x1f3f7;sec_deep_rnn 到目前为止&#xff0c;我们只讨论了具有一个单…

基于大数据的亚健康人群数据分析及可视化系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

在vsCode中将某个字符替换成换行符并且换行展示

将下面一行字符串中的“,”替换成换行符并且换行展示。 CTRLF键检索&#xff0c;查找和替换内容根据如下图输入即可。 点击全部替换后得到如下结果&#xff1a;

图文深入理解Oracle Network配置管理(二)

本篇图文深入介绍Oracle Network配置管理。 Oracle网络配置的目的 为了方便对Oracle 数据库进行管理&#xff0c;一般以下情况应该对Oracle进行网络配置。 • 在客户端对服务器端数据库进行管理&#xff08;网络客户端管理&#xff09; • 在一台服务器上管理多个数据库&…

性能测试学习1:性能测试的理论与目的,与功能测试的区别

一.什么是性能&#xff1f; 1&#xff09;性能&#xff1a;就是软件质量属性中的“效率”特性 2&#xff09;效率特性: ①时间特性&#xff1a;表示系统处理用户请求的响应时间【通俗来说&#xff0c;就是使用系统是否流畅】 ②资源特性&#xff1a;表示系统运行过程中&…

青动CRM-仓储云V1.1.2

多平台(微信公众号(高级授权)、微信小程序(高级授权)、H5网页(高级授权)、Android-App(高级授权)、iOS-App(高级授权))仓库管理系统&#xff0c;拥有强大的表单设计、多角色员工权限、出入库管理、仓库管理、送货管理、自定义审批流、绩效管理、客户管理、合同管理等功能。提供…

抖音支付回调验签 go 版本

序言 最近在做抖音小程序支付&#xff0c;由于抖音开放平台的文档写的较为简陋&#xff0c;让人踩了不少坑&#xff0c;在这里整理一下做小程序支付的整个过程&#xff0c;以通用交易系统为例子。 准备条件 1&#xff09;申请小程序&#xff0c;开通支付功能 这里需要明确你小…

Linux--基本指令

目录 1.ls指令 ​2.pwd指令 3.cd指令 4.tree指令 5.touch/mkdir/rmdir(rm)指令 6.cp/mv/cat/tac指令 7.head/tail/管道 8.date 9.find/which/grep 10.其它小知识 1.ls指令 补充知识&#xff1a; 2.pwd指令 补充知识&#xff1a; 3.cd指令 补充知识&#xff1a; 4.tree指令 …

Java语法-类和对象之抽象类和接口

1.抽象类 1.1 抽象类的概念 一个类中没有足够的信息来描述一个具体的对象,这样的类就是抽象类 比如: 从图中我们可以看出,只有继承了的类,我们产生的实例,调用的draw方法都是他们本身重写的draw方法,不会调用父类Shape的draw()方法,因此我们可以不管父类里面的draw()方法里面的…

MySQL 之多表设计详解

在实际应用场景中&#xff0c;我们经常需要处理包含多种数据实体及其之间复杂关系的业务逻辑&#xff0c;例如电商平台的用户、商品、订单&#xff0c;社交网络的用户、帖子、评论等等。如果将所有数据都堆砌在一张表中&#xff0c;不仅会造成数据冗余、难以维护&#xff0c;还…

MySQL 8.0.34 从C盘迁移到D盘

因为开始C盘够用&#xff0c;没注意mysql安装位置&#xff0c;如今C盘爆满&#xff0c;只能把mysql转移到D盘&#xff0c;以腾出更多的空间让我折腾。 一、关闭mysql服务 二、找到C盘MySQL安装文件和Data文件 1.找到C盘mysql bin文件目录安装文件路径&#xff1a; C:\Progra…

行为设计模式 -模板方法模式- JAVA

模板方法模式 一 .简介二. 案例2.1 抽象类&#xff08;Abstract Class&#xff09;2.2 具体子类&#xff08;Concrete Class&#xff09;2.3 测试 三. 结论3.1 优缺点3.2 适用场景3.3 要点 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接…

linux从入门到精通--从基础学起,逐步提升,探索linux奥秘(六)

linux从入门到精通–从基础学起&#xff0c;逐步提升&#xff0c;探索linux奥秘&#xff08;六&#xff09; 一、linux高级指令&#xff08;1&#xff09; 1、hostname指令 1&#xff09;作用&#xff1a;操作服务器的主机名&#xff08;读取、设置&#xff09; 2&#xff0…

huggingface的transformers与datatsets的安装与使用

目录 1.安装 2.分词 2.1tokenizer.encode&#xff08;&#xff09; 2.2tokenizer.encode_plus &#xff08;&#xff09; 2.3tokenizer.batch_encode_plus&#xff08;&#xff09; 3.添加新词或特殊字符 3.1tokenizer.add_tokens&#xff08;&#xff09; 3.2 token…

Python自动收发邮件的详细步骤与使用方法?

Python自动收发邮件教程&#xff1f;Python怎么实现收发邮件&#xff1f; Python作为一种强大的编程语言&#xff0c;提供了丰富的库和工具&#xff0c;使得自动收发邮件变得简单而高效。AokSend将详细介绍如何使用Python自动收发邮件&#xff0c;帮助读者掌握这一实用技能。 …

【ASE】第四课_护盾效果(有碰撞效果)

今天我们一起来学习ASE插件&#xff0c;希望各位点个关注&#xff0c;一起跟随我的步伐 今天我们来学习护盾的效果。 思路&#xff1a; 1.添加纹理贴图和法线贴图&#xff08;这里省略&#xff09; 2.添加护盾边缘顶点扰动效果&#xff0c;也可以理解成变形效果 3.添加碰撞…

关于frp Web界面-----frp Server Dashboard 和 frp Client Admin UI

Web 界面 官方文档&#xff1a;https://gofrp.org/zh-cn/docs/features/common/ui/ 目前 frpc 和 frps 分别内置了相应的 Web 界面方便用户使用。 客户端 Admin UI 服务端 Dashboard 服务端 Dashboard 服务端 Dashboard 使用户可以通过浏览器查看 frp 的状态以及代理统计信…

59 双向循环神经网络_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录双向RNN推理 总结以下为理论部分双向循环神经网络隐马尔可夫模型中的动态规划双向模型定义模型的计算代价及其应用 (**双向循环神经网络的错误应用**)小结练习 双向RNN 这里理解这个图的时候&#xff0c;不要把正向和逆向认为有上下的关系&a…

计算机毕业设计 基于Python的音乐平台的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

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