[数据结构]HashSet与LinkedHashSet的底层原理学习心得

news2025/1/21 18:52:53

我们区分list和set集合的标准是三个:有无顺序,可否重复,有无索引。
list的答案是:有顺序,可重复,有索引。这也就是ArrayList和LinkedList的共性
set的答案是:顺序内部再区分,不可以重复,无索引
我们接下来可以通过顺序的标准在set集合中进行再区分:
1.HashSet无顺序
2.LinkedHashSet有顺序
3.TreeSet可排序
Hashset的底层是哈希表->一种对于增删改查数据性能较好的数据结构
分析哈希表的构成:
Before JDK8:数组+链表
After JDK8:数组+链表+红黑树
哈希值:哈希表的灵魂所在->对象的整数表现形式
我们可以使用Object类中的hashcode方法来计算某个对象,运算结果是int类型的整数
问题来了:我们会拿对象的什么来进行计算?答案是:地址值
举个简单的例子:
现在有一个对象的地址值是0x0011 我通过这个地址值算出来一个哈希值:6794651616
然后我们在哈希表内添加元素的时候,注意,是在有索引的哈希表内添加元素的时候
这里我们避免一个认识误区:Hashset是没有索引的,但是构成Hashset的底层数据结构-哈希表是有索引的
我们将添加的元素在哈希表上的索引index的公式给出:
int index=(数组长度-1)&哈希值; 这里又证明了先前我们提出的哈希表底层原理:数组+链表+红黑树(JDK8以前)
那么此时我们会有一个问题:在Object类下的hashcode和复写后的hashcode产生的哈希值是一样的么?
答案是不一样的->
如果没有重写hashcode方法:不同对象计算出的哈希值是不同的
如果已经重写hashcode方法:不同的对象只要属性值相同,计算出的哈希值是一样的
这句话有点晦涩难懂,我们举个例子:
我需要构建一个学生信息管理系统,需要添加两个学生的名字进去 两个"小明"
如果我使用了object的hashcode 那么计算出的哈希值两次是不一样的 于是两个小明会被分配到不同的空间
但是如果我使用了重写后的hashcode 那么计算出的哈希值两次都是一样的 小明都会被储存到同一块区域当中去
但是注意:在小部分的情况下 不同的属性值或者不同的属性值计算机出的哈希值是一样的
这样的情况就是:哈希碰撞。因为int的范围是-21E到+21E 但是我现在创建50E个对象
一点有8E对象的哈希值是一样的 这样就会发生哈希碰撞 但是这样的极端概率是不高的
1.首先创建一个默认长度为16 默认加载因子是0.75的数组 数组名叫table
2.根据元素的哈希值跟数组的长度进行计算 计算出当前元素应存入的位置
公式: int index= (数组元素-1)&哈希值; 注意&是按位与
3.判断当前这个index对应的位置是否是Null 如果是null直接存入
4.如果当前位置不是Null 就说明已经有元素了 调用equals方法来比较属性值
例子:比如我要在Index为4的位置存入一个新的数据 但是4位置已经有数据了
5.如果属性值是一样:不存
  属性值不一样:存入数组,形成链表(JDK8以前 新的元素存入数组 老的元素挂在新元素下面)
                                (JDK8以后 新元素挂在老元素下面)
 加载因子是什么:数组的扩容时机:
 16x0.75=12元素满时添加
 6.红黑树的出现:我们先前谈到了在哈希表的相同位置添加元素会触发equals方法
 然后新的数据会挂载在哈希表的下面,其中红黑树就是一种jdk8以后产生的新挂载方法
 JDK8满足条件:a.链表长度超过8 b.数组长度>=64
 7.集合中储存的是自定义对象,必须要重写hashcode和equals方法
 前者的目的是为了属性值取代地址值,后者的目的是用属性值去进行比较
 我们构造链表和树的目的就是为了减少哈希碰撞

 Hashset为什么没有索引 Hash表上挂着许多的链表和红黑树 无法准确的表示具体的索引 因为一个index上可以挂着红黑树和链表 你如何确定二者的索引呢?

 学习问答题:
 Q1:Hashset的集合的底层数据结构是什么样的
 答案:HashTable(散列表)在JDK8以前 HashTable由数组和链表构成的 在JDK8以后由数组 链表 红黑树构成
 一个HashTable的所谓索引也就是Hashbucket(哈希桶),每一个哈希桶上可以挂载链表和红黑树
 Q2:Hashset添加元素的过程是如何的
 答案:如果哈希值对应的Hashbucket为Null 直接添加 else 再调用equals进行判断 一样就不存 不一样就存

 Q3 Hashset为什么取和存的顺序不一样
 答案:我遍历一个hashset其实是从hashtable的序号为0的hashbucket开始的 但是我存入一个元素到hashbucket是根据hashcode算出来的
 读取和存入执行的过程是不一样的
 Q4:HashSet为什么没有索引:
 答案:使用到了链表和红黑树挂在hashbucket上面 过于复杂无法用索引表示
 Q5:HashSet是用什么机制去保证去重复的?
 答案:重写后的equals方法

————————————————————————————————————————————
LinkedHashSet底层原理
有序,不重复,无索引
底层数据结构:哈希表+链表
import java.util.*;

public class Main{
    public static void main(String[] args){
 /*
 我们区分list和set集合的标准是三个:有无顺序,可否重复,有无索引。
 list的答案是:有顺序,可重复,有索引。这也就是ArrayList和LinkedList的共性
 set的答案是:顺序内部再区分,不可以重复,无索引
 我们接下来可以通过顺序的标准在set集合中进行再区分:
 1.HashSet无顺序
 2.LinkedHashSet有顺序
 3.TreeSet可排序
  */
        Set<String> Hash = new HashSet<>();
        Set<String> LinkedHash = new LinkedHashSet<>();
        Set<String> TreeSet = new TreeSet<>();
        Hash.add("张三");
        Hash.add("李四");
        Hash.add("王五");
        //1.ForEach+lambda表达式遍历
        Hash.forEach(s->System.out.println(s));
        //2.加强for循环遍历
        for(String s:Hash){
            System.out.println(s);
        }
        //3.迭代器遍历
        Iterator<String> it = Hash.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        //现在我们开始系统学习set,第一课:Hashset
        /*
        Hashset的底层是哈希表->一种对于增删改查数据性能较好的数据结构
        分析哈希表的构成:
        Before JDK8:数组+链表
        After JDK8:数组+链表+红黑树
        哈希值:哈希表的灵魂所在->对象的整数表现形式
        我们可以使用Object类中的hashcode方法来计算某个对象,运算结果是int类型的整数
        问题来了:我们会拿对象的什么来进行计算?答案是:地址值
        举个简单的例子:
        现在有一个对象的地址值是0x0011 我通过这个地址值算出来一个哈希值:6794651616
        然后我们在哈希表内添加元素的时候,注意,是在有索引的哈希表内添加元素的时候
        这里我们避免一个认识误区:Hashset是没有索引的,但是构成Hashset的底层数据结构-哈希表是有索引的
        我们将添加的元素在哈希表上的索引index的公式给出:
        int index=(数组长度-1)&哈希值; 这里又证明了先前我们提出的哈希表底层原理:数组+链表+红黑树(JDK8以前)
        那么此时我们会有一个问题:在Object类下的hashcode和复写后的hashcode产生的哈希值是一样的么?
        答案是不一样的->
        如果没有重写hashcode方法:不同对象计算出的哈希值是不同的
        如果已经重写hashcode方法:不同的对象只要属性值相同,计算出的哈希值是一样的
        这句话有点晦涩难懂,我们举个例子:
        我需要构建一个学生信息管理系统,需要添加两个学生的名字进去 两个"小明"
        如果我使用了object的hashcode 那么计算出的哈希值两次是不一样的 于是两个小明会被分配到不同的空间
        但是如果我使用了重写后的hashcode 那么计算出的哈希值两次都是一样的 小明都会被储存到同一块区域当中去
        但是注意:在小部分的情况下 不同的属性值或者不同的属性值计算机出的哈希值是一样的
        这样的情况就是:哈希碰撞。因为int的范围是-21E到+21E 但是我现在创建50E个对象
        一点有8E对象的哈希值是一样的 这样就会发生哈希碰撞 但是这样的极端概率是不高的
         */

        //1.创建一个对象
        Student s1=new Student("小明",23);
        Student s2=new Student("小明",23);

        //2.如果没有重写hashcode s1和s2返回的哈希值是不一样的
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        //你会发现在Student重写hashcode后的返回哈希值是一样的
        //这里的重写我们用alt+insert的便捷键来进行重写

        //哈希碰撞的特例:
        System.out.println("abc".hashCode());
        System.out.println("acD".hashCode());

        //Hashcode在JDK8以前的底层原理
        /*
        1.首先创建一个默认长度为16 默认加载因子是0.75的数组 数组名叫table
        2.根据元素的哈希值跟数组的长度进行计算 计算出当前元素应存入的位置
        公式: int index= (数组元素-1)&哈希值; 注意&是按位与
        3.判断当前这个index对应的位置是否是Null 如果是null直接存入
        4.如果当前位置不是Null 就说明已经有元素了 调用equals方法来比较属性值
        例子:比如我要在Index为4的位置存入一个新的数据 但是4位置已经有数据了
        5.如果属性值是一样:不存
          属性值不一样:存入数组,形成链表(JDK8以前 新的元素存入数组 老的元素挂在新元素下面)
                                        (JDK8以后 新元素挂在老元素下面)
         加载因子是什么:数组的扩容时机:
         16x0.75=12元素满时添加
         6.红黑树的出现:我们先前谈到了在哈希表的相同位置添加元素会触发equals方法
         然后新的数据会挂载在哈希表的下面,其中红黑树就是一种jdk8以后产生的新挂载方法
         JDK8满足条件:a.链表长度超过8 b.数组长度>=64
         7.集合中储存的是自定义对象,必须要重写hashcode和equals方法
         前者的目的是为了属性值取代地址值,后者的目的是用属性值去进行比较
         我们构造链表和树的目的就是为了减少哈希碰撞

         Hashset为什么没有索引 Hash表上挂着许多的链表和红黑树 无法准确的表示具体的索引 因为一个index上可以挂着红黑树和链表 你如何确定二者的索引呢?

         学习问答题:
         Q1:Hashset的集合的底层数据结构是什么样的
         答案:HashTable(散列表)在JDK8以前 HashTable由数组和链表构成的 在JDK8以后由数组 链表 红黑树构成
         一个HashTable的所谓索引也就是Hashbucket(哈希桶),每一个哈希桶上可以挂载链表和红黑树
         Q2:Hashset添加元素的过程是如何的
         答案:如果哈希值对应的Hashbucket为Null 直接添加 else 再调用equals进行判断 一样就不存 不一样就存

         Q3 Hashset为什么取和存的顺序不一样
         答案:我遍历一个hashset其实是从hashtable的序号为0的hashbucket开始的 但是我存入一个元素到hashbucket是根据hashcode算出来的
         读取和存入执行的过程是不一样的
         Q4:HashSet为什么没有索引:
         答案:使用到了链表和红黑树挂在hashbucket上面 过于复杂无法用索引表示
         Q5:HashSet是用什么机制去保证去重复的?
         答案:重写后的equals方法

        ————————————————————————————————————————————
        LinkedHashSet底层原理
        有序,不重复,无索引
        底层数据结构:哈希表+链表






         */

    }
}
 1.首先创建一个默认长度为16 默认加载因子是0.75的数组 数组名叫table
        2.根据元素的哈希值跟数组的长度进行计算 计算出当前元素应存入的位置
        公式: int index= (数组元素-1)&哈希值; 注意&是按位与
        3.判断当前这个index对应的位置是否是Null 如果是null直接存入
        4.如果当前位置不是Null 就说明已经有元素了 调用equals方法来比较属性值
        例子:比如我要在Index为4的位置存入一个新的数据 但是4位置已经有数据了
        5.如果属性值是一样:不存
          属性值不一样:存入数组,形成链表(JDK8以前 新的元素存入数组 老的元素挂在新元素下面)
                                        (JDK8以后 新元素挂在老元素下面)
         加载因子是什么:数组的扩容时机:
         16x0.75=12元素满时添加
         6.红黑树的出现:我们先前谈到了在哈希表的相同位置添加元素会触发equals方法
         然后新的数据会挂载在哈希表的下面,其中红黑树就是一种jdk8以后产生的新挂载方法
         JDK8满足条件:a.链表长度超过8 b.数组长度>=64
         7.集合中储存的是自定义对象,必须要重写hashcode和equals方法
         前者的目的是为了属性值取代地址值,后者的目的是用属性值去进行比较
         我们构造链表和树的目的就是为了减少哈希碰撞

         Hashset为什么没有索引 Hash表上挂着许多的链表和红黑树 无法准确的表示具体的索引 因为一个index上可以挂着红黑树和链表 你如何确定二者的索引呢?

         学习问答题:
         Q1:Hashset的集合的底层数据结构是什么样的
         答案:HashTable(散列表)在JDK8以前 HashTable由数组和链表构成的 在JDK8以后由数组 链表 红黑树构成
         一个HashTable的所谓索引也就是Hashbucket(哈希桶),每一个哈希桶上可以挂载链表和红黑树
         Q2:Hashset添加元素的过程是如何的
         答案:如果哈希值对应的Hashbucket为Null 直接添加 else 再调用equals进行判断 一样就不存 不一样就存

         Q3 Hashset为什么取和存的顺序不一样
         答案:我遍历一个hashset其实是从hashtable的序号为0的hashbucket开始的 但是我存入一个元素到hashbucket是根据hashcode算出来的
         读取和存入执行的过程是不一样的
         Q4:HashSet为什么没有索引:
         答案:使用到了链表和红黑树挂在hashbucket上面 过于复杂无法用索引表示
         Q5:HashSet是用什么机制去保证去重复的?
         答案:重写后的equals方法

        ————————————————————————————————————————————
        LinkedHashSet底层原理
        有序,不重复,无索引
        底层数据结构:哈希表+链表
import java.util.Objects;

public class Student
{
    private String name;
    private int age;

    public Student() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}
 @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

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

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

相关文章

SpringBootAdmin监控原理Actuator,自定义指标

SpringBootAdmin监控原理Actuator&#xff0c;自定义指标 文章目录 SpringBootAdmin监控原理Actuator&#xff0c;自定义指标actuator自定义info端点信息自定义Health端点信息自定义metrics端点信息端点的自定义 actuator JMX方式就是在cmd控制台输入jconsole&#xff0c;会弹出…

无代码集成航天信息:优化电商平台用户运营,提高CRM和广告推广的效能

无代码开发的集成优势 在数字化竞争愈发激烈的商业市场中&#xff0c;企业对于提高效率和优化用户运营的需求不断增长。无代码开发的集成解决方案&#xff0c;如航天信息电子发票&#xff0c;为企业提供了无需深入编程知识即可快速实现的系统对接能力。这种集成方式简化了技术…

shell编程系列(10)-使用paste拼接列

使用paste拼接列 前言使用paste拼接列拼接两个文件 结语 前言 在前面的文章中讲解了使用cut命令选择列&#xff0c;这篇文章我们介绍使用paste命令拼接列&#xff0c;其实这个命令的使用场景很有限&#xff0c;做科研的同学可能才会用到&#xff0c;但是却非常好用&#xff0c…

STM32---MDK工程创建

本节我们带领大家学习如何新建一个寄存器库版本MDK的详细步骤&#xff1b; 由于51单片机的学习时&#xff0c;所涉及的寄存器很少&#xff0c;所以往往几个头文件、驱动文件就可以完成相关的功能&#xff0c;但是对于STM32来讲&#xff0c;涉及的寄存器、头文件等都很多&#…

洛谷 P1998 阶乘之和 C++代码

前言 今天我们来做洛谷上的一道题目。 网址&#xff1a;[NOIP1998 普及组] 阶乘之和 - 洛谷 西江月夜行黄沙道中 【宋】 辛弃疾 明月别枝惊鹊&#xff0c;清风半夜鸣蝉。稻花香里说丰年&#xff0c;听取WA声一片。 七八个星天外&#xff0c;两三点雨山前。旧时茅店社林边&…

压缩docker在主机的虚拟磁盘容量

我们在windows里使用docker时会发现&#xff0c;即使我们已经删除了无用的镜像和容器&#xff0c;主机里挂在docker虚拟磁盘的那个盘&#xff0c;可用空间也没有增加&#xff0c;这是因为虚拟磁盘不会自动缩小&#xff0c;这里我分享一个可用的解决方案。 1.先通过docker回收空…

【Leetcode题单】(01 数组篇)刷题关键点总结01【数组的遍历】

【Leetcode题单】&#xff08;01 数组篇&#xff09;刷题关键点总结01【数组的遍历】&#xff08;4题&#xff09; Easy数组的遍历485. 最大连续 1 的个数 Easy495. 提莫攻击 Easy414. 第三大的数 Easy628. 三个数的最大乘积 Easy 大家好&#xff0c;这里是新开的LeetCode刷题系…

[力扣题]1.判断一棵树是否是平衡二叉树

1.判断一棵树是否是平衡二叉树 1.1 题目描述 给你一棵二叉树的根节点 root &#xff0c;请你判断这棵树是否是一棵 完全二叉树 。 在一棵 完全二叉树 中&#xff0c;除了最后一层外&#xff0c;所有层都被完全填满&#xff0c;并且最后一层中的所有节点都尽可能靠左。最后一层…

【物联网无线通信技术】ZigBee从理论到实践(CC2530)

文章延续之前【物联网无线通信技术】系列文章的风格&#xff0c;首先对ZigBee这种在物联网发展初期出现的无线通信技术进行了相关背景概念的介绍&#xff0c;并横向介绍了几款时间跨度比较大的ZigBee芯片。然后以CC2530为例&#xff0c;从硬件到软件介绍了ZigBee这中无线通信技…

C++跨目录include问题

不同文件夹下使用预处理器指示符#include 使用举例 假设我们有如下一个工程&#xff0c;其中包含了几个源代码和头文件&#xff0c;其中main.cpp是主源代码文件&#xff0c;里面含有main函数&#xff1a; 在foldder main中包含&#xff1a;func4.hpp&#xff0c;func4.cpp&am…

C语言结构体详解(二)(能看懂文字就能明白系列)文章很长,慢慢品尝

系列文章目录 第一章 结构体的介绍和基本使用 &#x1f31f; 个人主页&#xff1a;古德猫宁- &#x1f308; 信念如阳光&#xff0c;照亮前行的每一步 文章目录 系列文章目录&#x1f308; *信念如阳光&#xff0c;照亮前行的每一步* 前言前面一篇文章主要介绍了结构体的基础…

Java 使用对应arthas 调试程序

1、作用 使用 arthas 可以进行如下操作 ① 抓取对应函数的耗时结构&#xff0c;然后分析对应的代码优化代码 ② 抓取对应函数的 入参、出参函数 ③ 重放对应的函数执行 ④ 查询对应程序占用结构&#xff0c;比如 cpu, jvm ⑤ 查询对应的 执行最频繁的 线程 ⑥ 打印函数…

震惊!我和GPT玩了一天游戏·····

最近开始研究如何基于GPT构建一个游戏引擎&#xff0c;于是先从简单的文字游戏开始探索。 从最简单的选择机制、故事机制&#xff0c;完善成一个包括天气、事件、技能、属性、伙伴、建造系统的-生化危机版文字游戏-。 我唯一的体验是&#xff1a;AI游戏&#xff0c;大有可为! …

C++11--右值引用

目录 基本概念 左值和右值 左值引用和右值引用 右值引用使用场景和意义 左值引用使用场景 左值引用的短板 右值引用和移动语义 右值引用引用左值 右值引用的其他使用场景 完美转发 万能引用 完美转发保持值得属性 完美转发使用得场景 基本概念 左值和右值 什么…

二叉树链式结构的实现和二叉树的遍历以及判断完全二叉树

二叉树的实现 定义结构体 我们首先定义一个结构来存放二叉树的节点 结构体里分别存放左子节点和右子节点以及节点存放的数据 typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right; }BTNode;…

solidity实现ERC20代币标准

文章目录 IERC20ERC20Remix 编译部署 IERC20 IERC20 是 ERC20 标准的接口规范&#xff0c;它定义和规范了一个标准 ERC20 代币合约应该实现的功能。这里让 ERC20 合约直接继承自 IERC20 接口。 // SPDX-License-Identifier: MIT pragma solidity ^0.8.4;interface IERC20 { // …

2023软件测试大赛总结

2023软件测试大赛总结 文章目录 2023软件测试大赛总结软件下载方式比赛方式个人总结断言使用java基础 预选赛省赛国赛 软件下载方式 进入官网下载插件&#xff08;直接下载一个完整的Eclipse就可以,这样比较方便&#xff09; 需要保证jdk版本和要求的一致&#xff0c;不然可能…

【Spring Boot 源码学习】ApplicationContextInitializer 详解

Spring Boot 源码学习系列 ApplicationContextInitializer 详解 引言往期内容主要内容1. 初识 ApplicationContextInitializer2. 加载 ApplicationContextInitializer3. ApplicationContextInitializer 的初始化 总结 引言 书接前文《初识 SpringApplication》&#xff0c;我们…

LeetCode(50)有效的括号【栈】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 有效的括号 1.题目 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合…

Linux基础项目开发1:量产工具——页面系统(六)

前言&#xff1a; 前面我们已经将显示系统、输入系统、文字系统、UI系统全部搭建好了&#xff0c;下面就到了开发板页面的布局&#xff0c;也就是实现按钮在开发板页面上的每个位置&#xff0c;下面让我们一起实现页面的搭建与布局设计吧。 目录 一、数据结构抽象 page_manager…