JAVA_Set系列集合:HashSet、LinkedHashSet、TreeSet底层详解

news2024/11/17 3:32:57

先看看 Set 系列集合的位置:
image.png


Set 系列集合的特点:

  • 无序:存取顺序不一致
    • 如存入张三、李四、王五。而遍历获取到的是李四, 张三, 王五
  • 不重复:可以去除重复
  • 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素

Set 接口的实现类:

  • HashSet:无序、不重复、无索引
  • LinkedHashSet:有序、不重复、无索引
  • TreeSet:可排序、不重复、无索引

Set接口中的方法上基本上与Collection的API一致。
Collection 是单列集合的祖宗接口,它的功能是全部单列集合都可以使用的。
回顾一下:
image.png


--------------------------------

认识: HashSet :

  • HashSet 集合底层采取哈希表存储数据
  • 哈希表是一种对于增删改查数据 性能都较好的结构

哈希表组成:

  • jdk8 前:数组+链表
  • jdk8 及以后:数组+链表+红黑树
    • 所以 HashSet 底层和数组、链表、红黑树都有关系

哈希值:对象的整数表现形式

  • 它是根据hashCode方法算出来的int类型的整数
  • 该方法定义在Object类中,所有对象都可以调用,没有重写则默认使用地址值进行计算
  • 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值

对象的哈希值特点:

  • 如果没有重写 hashCode 方法,不同对象计算出的哈希值是不同的

image.png

  • 如果已经重写了 hashCode 方法,不同的对象只要属性值相同,计算出的哈希值就是一样的

image.png

  • 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)如下

image.png


jdk8 及以后的 HashSet 的底层原理。

  1. 创建 HashSet 集合后,会在底层创建一个长度为 16 的数组 table,并加载 负载因子为0.75 的 HashMap
    1. 这意味着当 HashSet 中的元素数量达到数组长度的 75% 时,数组会进行扩容(原有长度*2)操作,以保持较低的碰撞率和良好的性能。
  2. 根据元素的哈希值跟数组的长度计算出应存入的位置

int index=( 数组长度-1 ) & 哈希值
所以说第一个元素存入的位置不一定是 0 索引处,如下
image.png
获取元素时就从左到右遍历,所以说 HashSet 是无序的

  1. 判断当前位置是否为 null,如果是 null 直接存入
    1. 如果不是 null,表示有元素,则调用 equals 方法比较属性值
      1. 一样则不存,不一样则存入,形成链表
        1. 注意: jdk8 以前:新元素存入数组,老元素挂在新元素下面,jdk8及以后:新元素直接挂在老元素下面。如图:
        2. image.png
      2. 注意:当链表长度大于 8 并且 数组长度 大于等于 64 时,链表会变成红黑树。如图:
      3. image.png

注意点:

如果集合中存储的是自定义对象 ,必须要重写 hashCode 和 equals 方法(有的类已经重写过了,如 String Integer,会自动去重)不然 操作的都是地址值(一般来说,我们对于地址值是没有需求的)。

  • 重写 hashCode 是为了通过属性值计算哈希值
  • 重写 equals 是为了比较对象内部属性

用练习来理解:

Snipaste_2024-01-27_14-51-14.png

创建 Student 类–此时未重写 hashCode 和 equals 方法

public Student{
    private String name;
    private int age;

    //构造方法+set+get
	//此时未重写hashCode 和 equals 方法
}

创建测试类

Student1 s1=new Student("zhangsan",23);
Student1 s2=new Student("lisi",24);
Student1 s3=new Student("zhangsan",23);//已重复,不应该存入
//创建HashSet集合
HashSet <Student>set=new HashSet<>();

//重写hashCode和equals前,都能添加成功,这不是我们想要的
System.out.println(set.add(s1));//true
System.out.println(set.add(s2));//true
System.out.println(set.add(s3));//true

首先来说一下:为什么 s3 能添加成功:
因为此时在 Student 类中还未重写 hashCode ,所以使用的是地址值来获取的哈希值,由于不同对象地址值肯定不同,所以 s1 和 s3 被存在不同的位置上

此时在 Student 类中重写 hashCode 和 equals 方法(alt 和 insert 快捷键)

......
@Override
    public boolean equals(Object o) {

        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student1 student1 = (Student1) o;
        return age == student1.age && Objects.equals(name, student1.name);
    }

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

再来看测试类

Student1 s1=new Student("zhangsan",23);
Student1 s2=new Student("lisi",24);
Student1 s3=new Student("zhangsan",23);//属性重复,不应该存入
//创建HashSet集合
HashSet <Student>set=new HashSet<>();

/*重写hashCode和equals后,s3添加失败。                        */
System.out.println(set.add(s1));//true
System.out.println(set.add(s2));//true
System.out.println(set.add(s3));//false

说明一下此时为什么 s3 能添加成功:
重写 hashCode 后,通过属性来获取哈希值,而 s1 和 s3 的属性一样,所以会有一样的哈希值,所以存入的位置一样,此时就体现重写 equals 的作用了,s3 会和 s1 属性比较,发现一样,则不存


这个例子中,重写的 equals 方法拦截了相同哈希值,相同属性的对象的存入(实现了去重)
有时又不会拦截,如下哈希碰撞情况

它们有相同的哈希值,会放入同一个位置,重写的 equals 方法会比较它们的属性值,发现不一样,所以 acD 会挂在 abc 的下面,形成链表。


------------------------------------

认识: LinkedHashSet:

在集合体系中的位置:是 HashSet 的子类
image.png

特点:

  • 有序、不重复、无索引
    • 这里的有序指的是保证存储和取出的元素顺序一致

原理:

底层数据结构依旧是哈希表(是 HashSet 的子类)
使用双链表记录添加顺序
如图:遍历时就按记录的添加顺序来获取元素,
image.png

注意点:
在以后如果要数据去重,我们使用 HashSet 还是 LinkedHashSet?

默认使用 HashSet,如果 要求去重 且 存取有序,才使用LinkedHashSet

要知道:HashSet比LinkedHashSet效率更高


---------------------------------

认识: TreeSet:

在集合体系中的位置
Snipaste_2024-01-27_15-16-27.png

特点:

  • 可排序:按照元素的默认规则(有小到大)排序。
    • 自然排序:如果集合中的元素实现了Comparable接口(例如Integer、String等,默认已实现),TreeSet会根据元素自身的compareTo()方法提供的排序规则进行排序。 / **若是自定义类,要手动实现Comparable接口和compareTo()方法,否则找不到排序方法,报错
    • 定制排序:你也可以提供一个Comparator对象给TreeSet的对象用于定义自定义的排序逻辑。
    • 使用原则:默认使用第一种,当第一种不能满足需求就使用第二种
  • 不重复
  • 无索引

底层:

Tree Set集合底层是基于红黑树的数据结构实现排序的,
删改查性能都较好。


1. 自然排序Comparable的使用

排序练习题:
image.png

//创建TreeSet集合对象
TreeSet<Integer>treeSet=new TreeSet<>();

treeSet.add(1);
treeSet.add(4);
treeSet.add(2);
treeSet.add(5);
treeSet.add(3);


//自然排序 默认从小到大排序

//1.迭代器
Iterator<Integer> it = treeSet.iterator();
while (it.hasNext()){
    int i=it.next();//jdk5后自动装箱,拆箱
    System.out.print(i+" ");//1 2 3 4 5
}

System.out.println();

//2.增强for
for (Integer i : treeSet) {
    System.out.print(i+" ");
}

System.out.println();

//3.lambda
treeSet.forEach(i-> System.out.print(i+" "));

控制台:
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5


TreeSet集合 默认的排序规则(自然排序)

  • 对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序。
  • 对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序。如图:
    • image.png
    • 第一个字符相同则比较第二个,有字符默认 比 无字符大

TreeSet_对象自然排序_练习题:
image.png

要求:
1.根据年龄排序

可知 可以通过 自然排序 解决。又因为是自定义类,所以要手动实现Comparable接口和compareTo()方法,否则找不到排序方法,报错

Student 类

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

//构造方法+set+get+toString
   
}

测试类

public class Test {
    public static void main(String[] args) {
    //创建对象
    Student stu1 = new Student("zhangsan", 23);
    Student stu2 = new Student("lisi", 24);
    Student stu3 = new Student("wangwu", 25);
    Student stu4 = new Student("zhaoliu", 26);

    //创建集合
    TreeSet<Student> ts = new TreeSet<>();

    //添加对象
    ts.add(stu3);
    ts.add(stu2);
    ts.add(stu1);
    ts.add(stu4);
    //打印集合
    System.out.println(ts);
    }
}

此时打印会报错,因为集合内是自定义类,要手动给出排序方式:

步骤:

  1. 实现 Comparable 接口
    1. image.png
    2. image.png
    3. 再实现里面的 Compare To 方法image.png
    4. 并书写方法image.png

给出排序方式后:Student 类

package com.lt.treeset;

public class Student implements Comparable<Student>{
    private String name;
    private int age;
    .....
    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }


//this:表示当前要添加的元素
//o:表示已经在红黑树存在的元素
//返回值:
//负数:表示当前要添加的元素是小的,存左边
//正数:表示当前要添加的元素是大的,存右边
//0:表示当前要添加的元素已经存在,含弃
    @Override
    public int compareTo(Student o) {
        //只看年龄,升序,(降序就调换位置即可)
       return this.getAge()-o.getAge();
    }
}

测试类

public class Test {
    public static void main(String[] args) {
    //创建对象
    Student stu1 = new Student("zhangsan", 23);
    Student stu2 = new Student("lisi", 24);
    Student stu3 = new Student("wangwu", 25);
    Student stu4 = new Student("zhaoliu", 26);

    //创建集合
    TreeSet<Student> ts = new TreeSet<>();

    //添加对象
    ts.add(stu3);
    ts.add(stu2);
    ts.add(stu1);
    ts.add(stu4);
    //打印集合
    System.out.println(ts);
    }
}

控制台:
[Student{name = zhangsan, age = 23}, Student{name = lisi, age = 24}, Student{name = wangwu, age = 25}, Student{name = zhaoliu, age = 26}]

**若改变一下题目:
年龄相同则比较字母大小。
CompareTo 就可以这样写
image.png


其实元素存储的原理就是红黑树:
我们针对年龄来演示:
添加顺序:
image.png
红黑树添加规则:
image.png

-----开始:

  1. Snipaste_2024-01-27_21-33-38.png
  2. Snipaste_2024-01-27_21-33-48.png
  3. Snipaste_2024-01-27_21-34-30.png
  4. Snipaste_2024-01-27_21-34-58.png
  5. Snipaste_2024-01-27_21-36-10.png
  6. Snipaste_2024-01-27_21-36-32.png
  7. Snipaste_2024-01-27_21-37-56.png
  8. Snipaste_2024-01-27_21-38-53.png
  9. Snipaste_2024-01-27_21-39-08.png
  10. Snipaste_2024-01-27_21-40-31.png


2. 比较器排序(自定义):

创建TreeSet对象时候,传递比较器Comparator指定规则

练习:
image.png
要按照字符串长度来比较,用自然排序无法比较,所以要使用自定义
步骤
image.png

public class Test02 {
    public static void main(String[] args) {

        //创建集合
        TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {

            @Override
            //o1:表示当前要添加的元素
            //o2表示已经在红黑树存在的元素
            public int compare(String o1, String o2) {
                //按照长度排序
                int i = o1.length() - o2.length();
                //如果一样长则按照首字母排序
                if (i == 0) {
                    //调用默认的字符排序,就不会被丢弃
                    return o1.compareTo(o2);
                }
                return i;
            }
        });

        //添加
        ts.add("c");
        ts.add("qwer");
        ts.add("df");
        ts.add("ab");

        //打印:
        System.out.println(ts);//[c, ab, df, qwer]

    }
}

上面的匿名内部类可以用 Lambda 简化


练习题 :

TreeSet对象自定义排序练习题:

  • 存储老师对象并遍历,创建TreeSet集合使用带参构造方法
  • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母长度排序

Teacher 类:

public class Teacher {
    private String name;
    private int age;
//构造+set+get+ toString
}

测试类:

package com.lt.treeset;





import java.util.Comparator;
import java.util.TreeSet;

public class MyTreeSet4 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
            @Override
            public int compare(Teacher o1, Teacher o2) {
                //o1表示现在要存入的那个元素
                //o2表示已经存入到集合中的元素

                //主要条件
                int result = o1.getAge() - o2.getAge();
                //次要条件
                result = result == 0 ? o1.getName().length()-o2.getName().length() : result;
                return result;
            }
        });
        //创建老师对象
        Teacher t1 = new Teacher("zhangsan",22);
        Teacher t2 = new Teacher("lisi",22);
        Teacher t3 = new Teacher("wangwu",24);
        Teacher t4 = new Teacher("zhaoliu",24);
        //把老师添加到集合
        ts.add(t1);
        ts.add(t2);
        ts.add(t3);
        ts.add(t4);
        //遍历集合
        for (Teacher teacher : ts) {
            System.out.println(teacher);
        }
    }
}


自定义排序的应用场景:

当要给字符串长度排序

数字要从大到小排序


若排序方式一和方式二同时存在会以什么方式为准?
答:第二种


总结:
Snipaste_2024-01-27_16-26-38.png
Snipaste_2024-01-27_16-28-00.png


它们的 Set 系列的集合是基于 Map 接口的
HashSet:
Snipaste_2024-01-27_16-29-33.png
add 方法:
Snipaste_2024-01-27_16-29-52.png
LinkedHashSet:
Snipaste_2024-01-27_16-30-41.pngSnipaste_2024-01-27_16-30-55.png
TreeSet:
Snipaste_2024-01-27_16-31-21.png
后面讲 Map 接口再说。

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

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

相关文章

MTE内存扩展精讲与实战

思考 1、常见的内存安全问题有哪些&#xff1f;举例说明&#xff1f; 2、内存安全的软件缓解技术有哪些&#xff1f;在optee上的应用&#xff1f; 3、MTE下的内存安全性如何保证&#xff1f;空间安全性&#xff1f;时间安全性&#xff1f; 4、MTE的架构细节&#xff1f;硬件原…

8.14划分字母区间(LC763-M)(附.length,.length(),.size()使用原理)

算法&#xff1a; 在遍历的过程中相当于是要找每一个字母的边界&#xff0c;如果找到之前遍历过的所有字母的最远边界&#xff0c;说明这个边界就是分割点了。 此时前面出现过所有字母&#xff0c;最远也就到这个边界了。 步骤&#xff1a; 统计每一个字符最后出现的位置从…

【代码随想录-数组】长度最小的子数组

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

adb测试冷启动和热启动 Permission Denial解决

先清理日志 adb shell logcat -c 打开手机模拟器中的去哪儿网&#xff0c;然后日志找到包名和MainActivity adb shell logcat |grep Main com.Qunar/com.mqunar.atom.alexhome.ui.activity.MainActivity 把手机模拟器的去哪儿的进程给杀掉 执行 命令 adb shell am start -W…

2013年苏州大学837复试机试C/C++

2013年苏州大学复试机试 第一题 题目 假设有一堆数字&#xff08;小于100个&#xff09;需要对其做如下处理&#xff1a; 求平均数求标准差求方差 可用函数实现也可以不用 代码 #include <iostream> #include <sstream> //字符串流 #include <cmath> …

LabVIEW振动信号分析

LabVIEW振动信号分析 介绍如何使用LabVIEW软件实现希尔伯特-黄变换&#xff08;Hilbert-Huang Transform, HHT&#xff09;&#xff0c;并将其应用于振动信号分析。HHT是一种用于分析非线性、非平稳信号的强大工具&#xff0c;特别适用于旋转机械等复杂系统的振动分析。开发了…

【linux】Debian防火墙

Debian系统默认没有安装防火墙&#xff0c;但用户可以根据需要自行选择并安装一个防火墙以增强系统安全性。 一、查看Debian 桌面系统的防火墙是否关闭 在Debian及其他基于Linux的桌面系统中&#xff0c;防火墙功能通常是由iptables或nftables规则集控制的&#xff0c;而ufw&…

《WebKit技术内幕》学习之十五(3): Web前端之未来

3 Web应用和Web运行环境 3.1 Web应用 HTML5提供了强大的能力&#xff0c;而不是支持Web网页这么简单。就目前而言&#xff0c;它已经初步提供了支持Web网页向Web应用方向发展的能力。相对于本地应用&#xff08;Native Application&#xff09;&#xff0c;Web前端领域也能够…

如何在yolov8中验证时计算FPS

ultralytics-main/ultralytics/engine/validator.py文件下&#xff0c;第200行左右&#xff0c;添加如下代码 LOGGER.info(fFPS:{(1000 / sum(self.speed.values())):.2f}) speed.values()是一个字典&#xff0c;包括preprocess,inference,loss,postprocess的时间&#xff0c;所…

SpringSecurity(15)——OAuth2密码模式

工作流程 将用户和密码传过去&#xff0c;直接获取access_token&#xff0c;用户同意授权动作是在第三方应用上完成&#xff0c;而不是在认证服务器&#xff0c;第三方应用申请令牌时&#xff0c;直接带用户名和密码去向认证服务器申请令牌。这种方式认证服务器无法判断用户是…

【python自动化系列01】Openpyxl,操作Excel文件的利器

如果要批量操作Excel文件&#xff0c;使用最广泛的是 Openpyxl 库。这个库集成了Excel的所有操作&#xff0c;从创建Excel、保存Excel到设置Excel单元格字体、颜色都可以实现。下面开始学习 Openpyxl 的简单使用吧&#xff01;&#xff01;&#xff01; 安装 openpyxl 库 ope…

ensp winpcap无法安装

安装ensp的依赖软件winpcap无法安装 发现提示已有最新版本、找网上都是修改文件后缀名&#xff0c;测试后发现根本不行&#xff0c;有点扯 npcap是wireshark安装带的&#xff0c;通过卸载wireshark安装 ensp安装顺序应该先安装winpcap->wireshark->virtualbox->ens…

高中数学常识

一、大小关系 |x| > |sinx| 理由&#xff1a; 很明显&#xff0c;在圆内&#xff0c;弧长x>垂线sinx 3x、2x 、 1 2 \frac{1}{2} 21​x 理由&#xff1a; log 1 2 _\frac{1}{2} 21​​x、log 2 _2 2​x、 log 3 _3 3​x 二、(xy)? 的求法 利用二项式定理 三、平…

ThreadLocal内存泄漏示例

ThreadLocal内存泄漏是老生常谈的问题了&#xff0c;原理就不多说了&#xff0c;这里只简单回顾下 Thread类有个属性threadLocals&#xff0c;其实就是个map。 这个map的结构如下&#xff0c;key是ThreadLocal对象&#xff0c;是一个弱引用&#xff0c;value是调用threadLocal…

cmake工具的安装

1、简介 CMake 是一个开源的、跨平台的自动化建构系统。它用配置文件控制编译过程的方式和Unix的make相似&#xff0c;只是CMake并不依赖特定的编译器。CMake并不直接建构出最终的软件&#xff0c;而是产生标准的建构文件&#xff08;如 Unix 的 Makefile 或 Windows Visual C …

ZYNQ AC7020C的“点LED”实验

一、创建 Vivado 工程 1、启动 Vivado 2、在 Vivado 开发环境里点击“Create New Project”&#xff0c;创建一个新的工程 3、弹出一个建立新工程的向导&#xff0c;点击“Next” 4、在弹出的对话框中输入工程名和工程存放的目录。需要注意工程路径“Project location”不能有…

安科瑞宿舍安全用电监测:科技保障,安全无忧

在当今社会&#xff0c;电力已成为我们日常生活中不可或缺的一部分。然而&#xff0c;不正确的用电方式或管理不善可能会引发火灾等安全事故&#xff0c;给学生带来生命财产威胁。为了解决这一问题&#xff0c;安科瑞宿舍安全用电监测系统应运而生&#xff0c;为学生的用电安全…

[每日一题] 01.27 - 斐波那契数列

文章目录 打分斐波那契数列 打分 n int(input()) lis list(map(int,input().split())) a sum(lis) - min(lis) - max(lis) print(round(a / (n - 2),2))斐波那契数列 n int(input()) res [] for i in range(n):res.append(int(input()))Max max(res) lis [1,1] for i in…

高精度加减乘除算法模板

高精度加减乘除算法模板 高精度加法算法模板模版题 高精度减法算法模板模版题 高精度乘法算法模板模版题 高精度除法算法模板模版题 高精度加法算法模板 首先&#xff0c;我们要知道高精度算法是C才用的&#xff0c;Java中是不需要高精度算法的 高精度加法&#xff1a; 两个大的…

K8S搭建(centos)二、服务器设置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…