• 【数据结构】HashSet的底层数据结构



    在这里插入图片描述

    🐌个人主页: 🐌 叶落闲庭
    💨我的专栏:💨
    c语言
    数据结构
    javaEE
    操作系统
    Redis

    石可破也,而不可夺坚;丹可磨也,而不可夺赤。


    • Set 系列集合
      • 无序:存取顺序不一致
      • 不重复:可以去除重复
      • 无索引:没有带索引的方法,所以不能使用普通fo循环遍历,也不能通过索引来获取元素

    一、 HashSet 集合的底层数据结构

    • HashSet :无序、不重复、无索引
    • HashSet 底层是采用哈希表存储数据的,哈希表是一种对于增删改查数据性能都较好的结构
    • 哈希表在JDK8之前是由数组+链表组成的,在JDK8之后是由数组+链表+红黑树组成的
    • 在哈希表中,最重要的是哈希值,哈希值就是对象的整数表现形式,HashSet 在存数据的时候,会根据数组长度和哈希值计算出要存入的位置,哈希值是根据hashCode()方法计算出来的int型的整数,hashCode()方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算,一般情况下,自定义的对象都要重写hashCode()方法,利用对象内部的属性值计算哈希值。
    int index = (数组长度 - 1) & 哈希值;
    
    • 1
    • 对象的哈希值特点:
      • 如果没有重写hashCode()方法,同一个类创建的不同对象计算出的哈希值是不同的
    public class Student {
        private String name;
        private int age;
    
        public Student() {
        }
    
        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 + "}";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    public static void main(String[] args) {
            //创建对象
            //没有重写hashCode方法,计算出的哈希值是不同的
            Student s1 = new Student();
            Student s2 = new Student();
            System.out.println(s1.hashCode());//460141958
            System.out.println(s2.hashCode());//1163157884
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    • 如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
    public class Student {
        private String name;
        private int 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);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
    
        public Student() {
        }
    
        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 + "}";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    public static void main(String[] args) {
            //创建对象
            //如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
            Student s1 = new Student();
            Student s2 = new Student();
            System.out.println(s1.hashCode());//961
            System.out.println(s2.hashCode());//961
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    • 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
    public static void main(String[] args) {
            //在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
            System.out.println("abc".hashCode());//96354
            System.out.println("acD".hashCode());//96354
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    二、 HashSet 添加元素的过程

    HashSet在JDK8以后的底层原理:

    • 创建一个默认长度为16,默认加载因子为0.75的数组,数组名为table
    • 根据元素的哈希值跟数组长度计算处应存入的位置
    int index = (数组长度 - 1) & 哈希值;
    
    • 1
    • 判断当前位置是否为null,如果是null,则直接存入
    • 如果当前位置不是null,表示有元素,则调用equals()方法与当前位置的属性进行比较
      • 如果相同,则舍弃不存
      • 如果不同,则存入数组,形成链表
    • JDK8以前,新元素存入数组,老元素挂在新元素下面形成链表
    • JDK8之后,新元素挂在老元素下面形成链表
    • 当链表长度大于8且数组长度大于等于64时,当前链表会自动转成红黑树
    • 如果集合中存储的是自定义对象,必须重写 hashCode 和 equals 方法

    三、 HashSet 为什么存和取的顺序不一样

    HashSet 在遍历的时候是从数组的0索引开始遍历的,每个索引下都要遍历该索引下对应的链表,当遍历到一个索引,这个索引的值为空时,会跳过,遍历下一个索引,该索引下对应有链表时,就会遍历这个链表,若是红黑树,也会遍历这个红黑树,按这个原则遍历数组,因为某个索引下对应的元素不一定就是存入时的顺序,所以HashSet 在存和取时的顺序也不一定相同。


    在这里插入图片描述


    四、 HashSet 为什么没有索引

    HashSet 是由数组+链表+红黑树组成的,数组是有索引的,但是存在HashSet 中的元素是通过链表或红黑树的形式挂在数组的每个索引下的,也就是每个索引可能对应多个元素,所以HashSet 不能由索引找到对应的元素。


    在这里插入图片描述


    五、 HashSet 的去重机制

    HashSet 是通过HashCode计算出每个元素应该存放的位置,,然后通过equals方法去比较对象内部的属性值是否一致,保证不会出现重复的元素。

    在这里插入图片描述


  • 相关阅读:
    兼容PyTorch,25倍性能加速,OneFlow“超速”了
    10、斐波那契数列
    WPF的UI布局
    innovus笔记——mem(macro)太多,怎么才能摆出符合数据流的mem呢?
    01.OpenWrt-写在前面
    WEB 自动化神器 TestCafe(一)—安装和入门篇
    探索高效开发神器:Blackbox AI(免费编程助手)
    【JX-18A/1信号继电器】
    实操新项目丨手把手带你开发疫情防控系统
    ip命令
  • 原文地址:https://blog.csdn.net/qq_64743563/article/details/133513560