哈希表(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相当于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.mfbz.cn/a/885913.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DC00025【含论文】基于协同过滤推荐算法springboot视频推荐管理系统

1、项目功能演示 DC00025【含文档】基于springboot短视频推荐管理系统协同过滤算法视频推荐系统javaweb开发程序设计vue 2、项目功能描述 短视频推荐系统分为用户和系统管理员两个角色 2.1 用户角色 1、用户登录、用户注册 2、视频中心&#xff1a;信息查看、视频收藏、点赞、…

1.1.4 计算机网络的分类

按分布范围分类&#xff1a; 广域网&#xff08;wan&#xff09; 城域网&#xff08;man&#xff09; 局域网&#xff08;lan&#xff09; 个域网&#xff08;pan&#xff09; 注意&#xff1a;如今局域网几乎采用“以太网技术实现”&#xff0c;因此“以太网”几乎成了“局域…

Python核心知识:pip使用方法大全

什么是 pip&#xff1f; pip 是 Python 的包管理工具&#xff0c;允许用户安装、升级和管理 Python 的第三方库和依赖。它极大地简化了开发过程&#xff0c;使开发者可以轻松地获取并安装所需的软件包。pip 已成为 Python 项目中最常见的包管理工具&#xff0c;并且自 Python …

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】 一、上篇回顾二、项目准备2.1 准备模板项目2.2 支持计时功能2.3 配置UART4引脚2.4 支持printf重定向到UART42.5 支持printf输出浮点数2.6 支持printf不带\r的换行2.7 支持ccache编译缓存 三、TFLM集成3.1 添加tfli…

Ceph RocksDB 深度调优

介绍 调优 Ceph 可能是一项艰巨的挑战。在 Ceph、RocksDB 和 Linux 内核之间&#xff0c;实际上有数以千计的选项可以进行调整以提高存储性能和效率。由于涉及的复杂性&#xff0c;比较优的配置通常分散在博客文章或邮件列表中&#xff0c;但是往往都没有说明这些设置的实际作…

C# 相等性检测方法差异分析(==,Equals,ReferenceEquals)

先给结论&#xff1a; 对于每种类型创建2个一样的数据&#xff0c;比较结果如下表所示&#xff1a; 数据类型EqualsReferenceEqualsint(值类型)√√引用类型引用类型&#xff08;带override&#xff09;以operator 实现为准以Equals覆写为准struct必须实现操作符√struct&…

Android 12系统源码_输入系统(三)输入事件的加工和分发

前言 上一篇文章我们具体分析了InputManagerService的构造方法和start方法&#xff0c;知道IMS的start方法经过层层调用&#xff0c;最终会触发Navite层InputDispatcher的start方法和InputReader的start方法。InputDispatcher的start方法会启动一个名为InputDispatcher的线程&…

基于深度学习的点云处理模型PointNet++学习记录

前面我们已经学习了Open3D&#xff0c;并掌握了其相关应用&#xff0c;但我们也发现对于一些点云分割任务&#xff0c;我们采用聚类等方法的效果似乎并不理想&#xff0c;这时&#xff0c;我们可以想到在深度学习领域是否有相关的算法呢&#xff0c;今天&#xff0c;我们便来学…

给Windows系统设置代理的操作方法

一、什么是代理 网络代理是一种特殊的网络服务&#xff0c;允许一个网络终端通过这个服务与另一个网络终端进行非直接的连接&#xff0c;而提供代理服务的电脑系统或其它类型的网络终端被称为代理服务器。 代理服务器是网络信息的中转站&#xff0c;代理服务器就像是一个很大的…

DBC差异比较工具DBCCompare_原理介绍(四)

DBC比对工具UI图片 DBC比对工具&#xff1a;功能详解与源码分析 在现代汽车开发和诊断过程中&#xff0c;DBC&#xff08;Database Container&#xff09;文件扮演着至关重要的角色。它们详细描述了CAN&#xff08;Controller Area Network&#xff09;网络中各消息和信号的详…

GB28181信令交互流程及Android端设备对接探讨

GB28181规范必要性 好多开发者在做比如执法记录仪、智能安全帽、智能监控等设备端视频回传技术方案选型的时候&#xff0c;不清楚到底是用RTSP、RTMP还是GB28181&#xff0c;对GB28181相对比较陌生&#xff0c;我们就GB28181规范的必要性&#xff0c;做个探讨&#xff1a; 实现…

【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第十八章 Linux编写第一个自己的命令

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

企业安全策略制定

如今&#xff0c;网络安全是所有组织的必需品&#xff0c;而不是奢侈品。现代企业面临着针对其数据、网络和系统的复杂且不断演变的威胁。 即使一个漏洞也可能导致严重违规、财务损失和声誉受损。正如堡垒依靠多层防御共同作用一样&#xff0c;公司的安全措施必须作为一个整体…

【学习笔记】手写 Tomcat 六

目录 一、线程池 1. 构建线程池的类 2. 创建任务 3. 执行任务 测试 二、URL编码 解决方案 测试 三、如何接收客户端发送的全部信息 解决方案 测试 四、作业 1. 了解工厂模式 2. 了解反射技术 一、线程池 昨天使用了数据库连接池&#xff0c;我们了解了连接池的优…

渗透测试--文件上传常用绕过方式

文件上传常用绕过方式 1.前端代码&#xff0c;限制只允许上传图片。修改png为php即可绕过前端校验。 2.后端校验Content-Type 校验文件格式 前端修改&#xff0c;抓取上传数据包&#xff0c;并且修改 Content-Type 3.服务端检测&#xff08;目录路径检测&#xff09; 对目…

医院体检管理系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;体检分类管理&#xff0c;体检套餐管理&#xff0c;体检预约管理&#xff0c;体检报告管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;体检套餐&a…

四、Drf认证组件

四、Drf认证组件 4.1 快速使用 from django.shortcuts import render,HttpResponse from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.authentication import BaseAuthentication from rest_framework.exception…

数据结构:将复杂的现实问题简化为计算机可以理解和处理的形式

整句话的总体意义是&#xff0c;**数据结构是用于将现实世界中的实体和关系抽象为数学模型&#xff0c;并在计算机中表示和实现的关键工具**。它不仅包括如何存储数据&#xff0c;还包括对这些数据的操作&#xff0c;能够有效支持计算机程序的运行。通过这一过程&#xff0c;数…

利用PDLP扩展线性规划求解能力

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Java项目实战II基于Java+Spring Boot+MySQL的甘肃非物质文化网站设计与实现(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者 一、前言 甘肃省作为中国历史文化名省&#xff0c;拥有丰富的非物质文化遗产资源&#xff0c;涵盖表演艺术、手…