Java中Set集合的使用和底层原理

news2024/12/22 19:47:10

文章目录

  • Set系列集合介绍
    • Set集合概述
    • HashSet无序原理
    • Set集合对象去重
    • LinkedHashSet
    • TreeSet排序规则

Set系列集合介绍

Set集合概述

Set系列集合特点:

无序:存取数据的顺序是不一定的, 当数据存入后, 集合的顺序就固定下来了

不重复:可以去除重复

无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。

Set集合实现类特点:

HashSet : 无序、不重复、无索引。

public static void main(String[] args) {
  	// 无序, 不重复, 无索引
    Set<String> sets = new HashSet<>();
    sets.add("MySQL");
    sets.add("MySQL");
    sets.add("JAVA");
    sets.add("JAVA");
    sets.add("HTML");
    sets.add("HTML");
    sets.add("Vue");
    sets.add("Vue");
    System.out.println(sets); // [JAVA, MySQL, Vue, HTML]
}

LinkedHashSet:有序、不重复、无索引。

public static void main(String[] args) {
  	// 有序、不重复、无索引
    Set<String> sets = new LinkedHashSet<>();
    sets.add("MySQL");
    sets.add("MySQL");
    sets.add("JAVA");
    sets.add("JAVA");
    sets.add("HTML");
    sets.add("HTML");
    sets.add("Vue");
    sets.add("Vue");
    System.out.println(sets); // [MySQL, JAVA, HTML, Vue]
}

TreeSet:排序: 默认升序、不重复、无索引。

public static void main(String[] args) {
    // 排序、不重复、无索引
    Set<Integer> sets = new TreeSet<>();
    sets.add(10);
    sets.add(10);
    sets.add(20);
    sets.add(20);
    sets.add(30);
    sets.add(30);
    sets.add(40);
    sets.add(40);
    sets.add(50);
    sets.add(50);
    System.out.println(sets); // [10, 20, 30, 40, 50]
}

Set集合的功能上基本上与Collection的API一致, Set集合没有扩展额外的API

HashSet无序原理

HashSet集合底层采取哈希表存储的数据。

哈希表是一种对于增删改查数据性能都较好的结构。

哈希表的组成:

JDK8之前的,底层使用数组+链表组成

JDK8开始后,底层采用数组+链表+红黑树组成。

哈希表是一种对于增删改查数据性能都较好的结构。

在了解哈希表之前需要先理解哈希值的概念

哈希值:

是JDK根据对象的地址,按照某种规则算出来的int类型的数值。

获取哈希值: 通过Object类的API:

public int hashCode():返回对象的哈希值

对象的哈希值特点:

同一个对象多次调用hashCode()方法返回的哈希值是相同的

public static void main(String[] args) {
    String address = "成都市";
    System.out.println(address.hashCode()); // 25299637
    System.out.println(address.hashCode()); // 25299637
    System.out.println(address.hashCode()); // 25299637
}

默认情况下,不同对象的哈希值是不同的。

public static void main(String[] args) {
    String address = "成都市";
    System.out.println(address.hashCode()); // 25299637
    System.out.println(address.hashCode()); // 25299637
    System.out.println(address.hashCode()); // 25299637

    String address2 = "重庆市";
    System.out.println(address2.hashCode()); // 36643529
}

JDK8之前的版本HashSet原理解析:数组 + 链表 +(结合哈希算法), 详细流程如下:

  1. 底层会默认创建一个默认长度16的数组,数组名table

在这里插入图片描述

  1. 根据元素的哈希值数组的长度求余计算出应存入的位置(哈希算法)

例如数组长度是16, 哈希值与16取余, 得出的结果一定是0到15之间的数字

  1. 判断当前位置是否为null,如果是null直接存入
  2. 如果位置不为null,表示有元素,则调用equals方法比较
  3. 如果一样,则不存,如果不一样,则存入数组

在JDK 7中, 新元素占老元素位置,并且新元素会指向老元素

在JDK 8中, 新元素挂在老元素下面

JDK8之后的版本HashSet原理解析:

底层结构:哈希表(数组、链表、红黑树的结合体)

当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。

在这里插入图片描述

JDK8开始后,哈希表对于红黑树的引入进一步提高了操作数据的性能。

Set集合对象去重

HashSet去重注意点:

Set集合在比较两个对象时, 默认比较的是两个对象的地址是否一致, 若地址不同则认为是两个不同的对象;

而如果希望Set集合认为2个内容一样的对象是重复的,则需要自己重写对象的hashCode()和equals()方法

我们来看下面这样一个案例:

需求: 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象

分析:

  1. 定义学生类,创建Set集合对象, 创建学生对象
  2. 把学生添加到集合
  3. 在学生类中重写两个方法,hashCode()和equals(),自动生成即可
  • 步骤一: 定义学生类
public class Student {
    private String name;
    private int age;
    private int id;

    // 构造器
    public Student() {};

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

    // getter和setter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
  
    // 重写toString方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }
}
  • 步骤二: 创建学生对象和HashSet集合, 并将学生对象存入HashSet集合中, 如下代码:
public class Test {
    public static void main(String[] args) {
        // 创建集合存储学生对象
        Set<Student> students = new HashSet<>();
        // 创建学生对象
        Student stu1 = new Student("小明", 18, 101);
        Student stu2 = new Student("小明", 18, 101);
        Student stu3 = new Student("小王", 20, 102);
        // 将学生对象添加到集合中
        students.add(stu1);
        students.add(stu2);
        students.add(stu3);

        System.out.println(students);
        // 打印结果如下: 
        //  [Student{name='小明', age=18, id=101}, 
        //  Student{name='小明', age=18, id=101}, 
        //  Student{name='小王', age=20, id=102}]
    }
}
  • 步骤三: 我们发现步骤二代码中, stu1和stu2对象的内容完全一样, 但是由于对象的地址不一样, 会被当成两个不同的对象存入到集合中; 因此我们需要在学生类中重写两个方法,hashCode()和equals(),自动生成即可
@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 && id == student.id && Objects.equals(name, student.name);
}

@Override
public int hashCode() {
  	// 该方法传入的参数相同, 就会返回相同的哈希值
    return Objects.hash(name, age, id);
}
  • 步骤四: 此时再将stu1和stu2对象存入集合, 由于内容一样就会被去掉重复的
public class Test {
    public static void main(String[] args) {
        // 创建集合存储学生对象
        Set<Student> students = new HashSet<>();
        // 创建学生对象
        Student stu1 = new Student("小明", 18, 101);
        Student stu2 = new Student("小明", 18, 101);
        Student stu3 = new Student("小王", 20, 102);
        // 将学生对象添加到集合中
        students.add(stu1);
        students.add(stu2);
        students.add(stu3);

        System.out.println(students);
        //  [Student{name='小王', age=20, id=102}, Student{name='小明', age=18, id=101}]
    }
}

LinkedHashSet

LinkedHashSet集合概述和特点:

有序、不重复、无索引。

这里的有序指的是保证存储和取出的元素顺序一致

原理

底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

在这里插入图片描述

TreeSet排序规则

TreeSet集合特点:

不重复、无索引、可排序

可排序:按照元素的大小默认升序(有小到大)排序。

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

注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序

TreeSet默认排序规则:

对于数值类型:Integer , Double,官方默认按照大小进行升序排序。

public static void main(String[] args) {
    Set<Integer> sets1 = new TreeSet<>();
    sets1.add(50);
    sets1.add(10);
    sets1.add(30);
    sets1.add(20);
    System.out.println(sets1); // [10, 20, 30, 50]

    Set<Double> sets2 = new TreeSet<>();
    sets2.add(10.11);
    sets2.add(20.22);
    sets2.add(43.22);
    sets2.add(8.22);
    System.out.println(sets2); // [8.22, 10.11, 20.22, 43.22]
}

对于字符串类型:默认按照首字符的编号升序排序。

public static void main(String[] args) {
    Set<String> sets = new TreeSet<>();
    sets.add("bbb");
    sets.add("eee");
    sets.add("aaa");
    sets.add("ccc");
    System.out.println(sets); // [aaa, bbb, ccc, eee]
}

对于自定义类型如Student对象,TreeSet无法直接排序, 需要制定排序规则; 例如下面代码中向集合中添加学生类, TreeSet是无法进行排序的, 会崩溃报错

// 错误演示
public static void main(String[] args) {
    // 创建学生对象
    Student stu1 = new Student("小明", 18, 101);
    Student stu2 = new Student("小赵", 18, 102);
    Student stu3 = new Student("小王", 18, 103);
    // 创建集合
    Set<Student> students = new TreeSet<>();
    students.add(stu1);
    students.add(stu2);
    students.add(stu3);
    System.out.println(students);
}

自定义排序规则: TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则

方式一: 让自定义的类(如学生类)实现Comparable接口, 并重写compareTo方法来定制比较规则。

// 实现Comparable接口
public class Student implements Comparable<Student> {
  	// 其他代码...
  
  	// 重写compareTo方法
    @Override
    public int compareTo(Student o) {
        // 例如按照id进行排序
        return this.id - o.id;
    }
}
public static void main(String[] args) {
    // 创建学生对象
    Student stu1 = new Student("小明", 18, 101);
    Student stu2 = new Student("小赵", 18, 102);
    Student stu3 = new Student("小王", 18, 103);
    // 创建集合
    Set<Student> students = new TreeSet<>();
    students.add(stu1);
    students.add(stu2);
    students.add(stu3);
    System.out.println(students);
    // 打印结果: 按照id升序
    // [Student{name='小明', age=18, id=101}, 
    // Student{name='小赵', age=18, id=102}, 
    // Student{name='小王', age=18, id=103}]
}

方式二: TreeSet集合有参数构造器自带比较器对象,来进行定制比较规则, 并且该方法如果和方式一同时出现, 会优先使用此方法的比较规则。

public class SetDemo {
    public static void main(String[] args) {
        // 创建学生对象
        Student stu1 = new Student("小明", 18, 101);
        Student stu2 = new Student("小赵", 18, 102);
        Student stu3 = new Student("小王", 18, 103);
        // 创建集合
        // 方式二: 使用构造器自带的比较器对象
        Set<Student> students = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o2.getId() - o1.getId();
            }
        });
        students.add(stu1);
        students.add(stu2);
        students.add(stu3);
        System.out.println(students);
        // 打印结果: 按照id降序
        // [Student{name='小王', age=18, id=103},
        // Student{name='小赵', age=18, id=102},
        // Student{name='小明', age=18, id=101}]
    }
}
  • 并且可以使用Lambda表达式简化代码
public class SetDemo {
    public static void main(String[] args) {
        // 创建学生对象
        Student stu1 = new Student("小明", 18, 101);
        Student stu2 = new Student("小赵", 18, 102);
        Student stu3 = new Student("小王", 18, 103);
        // 创建集合
        // 方式二: 使用构造器自带的比较器对象
        Set<Student> students = new TreeSet<>((Student o1, Student o2) -> o2.getId() - o1.getId());
        
        students.add(stu1);
        students.add(stu2);
        students.add(stu3);
        System.out.println(students);
        // 打印结果: 按照id降序
        // [Student{name='小王', age=18, id=103},
        // Student{name='小赵', age=18, id=102},
        // Student{name='小明', age=18, id=101}]
    }
}

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

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

相关文章

HTML期末学生大作业:中华传统文化【苏绣手工艺】带psd设计图(15页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

线程的基本操作以及线程的状态

目录 &#x1f433;今日良言:得之坦然&#xff0c;失之淡然&#xff0c;争取必然&#xff0c;顺其自然。 &#x1f42f;一、线程的基本操作 &#x1f42d;1.线程的创建 &#x1f42d;2.线程的中断 &#x1f42d;3.线程的等待 &#x1f42d;4.获取线程实例 &#x1f42d;…

[附源码]计算机毕业设计学分制环境下本科生学业预警帮扶系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【POJ No. 3264】区间最值差 Balanced Lineup

【POJ No. 3264】区间最值差 Balanced Lineup 北大OJ 题目地址 其实这道题 之前也做过一次了 http://t.csdn.cn/0YZgC 不过上次是用ST 做的。这次换做 分块来实现。 【题意】 每天挤奶时&#xff0c;约翰的N 头奶牛&#xff08;1≤N≤50,000&#xff09;都以相同的顺序排队…

【网络工程】7、实操-万达酒店综合项目(一)

接上篇《6、防火墙介绍及配置实操》 之前我们讲解了防火墙的基础知识以及相应的实操案例&#xff0c;本篇我们结合之前的交换机、路由器及防火墙的知识&#xff0c;进行一个酒店网络项目的实战。 本篇主要介绍一下酒店网络项目的整体需求文档。 一、项目背景 为规范万达美华…

tensorflow fashion_mnist数据集模型训练及预测

✨ 博客主页&#xff1a;小小马车夫的主页 ✨ 所属专栏&#xff1a;Tensorflow 文章目录前言一、环境二、fashion_mnist数据集介绍三、fashion_mnist数据集下载和展示四、数据预处理五、构建模型和训练模型六、模型预测总结前言 前面介绍mnist手写数字集训练&#xff0c;本文对…

自制肥鲨HDO2电源降压延长线,支持3S~6S动力电池

自制肥鲨HDO2电源降压延长线&#xff0c;支持3S~6S动力电池1. 问题源由2. 破题思路2.1 10元大钞搞定2.2 两个毛爷爷搞定3. 解决方案4. 最终延长线产出4.1 裸照4.2 成品5. 花絮1. 问题源由 源由&#xff1a; 电池盒电源线接触不良。 肥鲨眼镜的电源盒问题由来已久&#xff0c;…

SecureCRT隧道,跳板机+端口转发,内网穿透

背景 ServerA(Linux系统)&#xff1a; 内网&#xff1a;192.168.111.201 公网&#xff1a;10.121.8.88&#xff08;虚构的ip方便理解&#xff09; ServerB&#xff1a; 内网&#xff1a;192.168.111.202 本机&#xff1a; 安装有SecureCRT软件 注意上图中的箭头。箭头指向可…

Android动画——使用动画启动Activity

1、使用动画启动Activity概述 我们在Android开发应用时&#xff0c;会遇到一个页面跳转到另一个页面的情况&#xff0c;这时候我们如果使用动画过渡会使得页面更加的流畅。这是一个滑动式的进入和退出的动画可以看到Android的过渡动画可以在不同状态之间建立视觉联系。您可以为…

find 命令这 7 种高级用法

可以很肯定地说&#xff0c;find 命令是 Linux 后台开发人员必须熟知的操作之一&#xff0c;除非您使用的是 Windows Server。 对于技术面试&#xff0c;它也是一个热门话题。让我们看一道真题&#xff1a; 如果你的 Linux 服务器上有一个名为 logs 的目录&#xff0c;如何删…

MySQL性能调优——索引篇

MySQL为什么会选错索引 使用explain命令可以查看查询语句使用了具体使用了哪个索引&#xff0c;比如 explain select * from t where a between 10000 and 20000;查询结果如图所示。 选择索引是优化器的工作 优化器选择索引的目的是想找到一个最优的执行方案&#xff0c;并…

08_线程池

08_线程池前言Callable接口ThreadPoolExecutor**为什么用线程池****线程池的好处**架构说明创建线程池底层实现线程池的重要参数拒绝策略线程池底层工作原理问题二: 线程池使用过吗?谈谈在生产上如何设置的参数?线程池的拒绝策略你谈谈?工作中单一的/固定数的/可变数的三种创…

设计模式 之 行为型模式

设计模式 之 行为型模式 模式 & 描述包括行为型模式 这些设计模式特别关注对象之间的通信。责任链模式&#xff08;Chain of Responsibility Pattern&#xff09; 命令模式&#xff08;Command Pattern&#xff09;解释器模式&#xff08;Interpreter Pattern&#xff09;…

Web 性能指标

Web 性能指标 对于 Web 开发人员来说&#xff0c;如何衡量一个 Web 页面的性能一直是一个难题。 最初&#xff0c;我们使用 Time to First Byte、DomContentLoaded 和 Load 这些衡量文档加载进度的指标&#xff0c;但它们不能直接反应用户视觉体验。 为了能衡量用户视觉体验…

[附源码]计算机毕业设计springboot志愿者服务平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[数据结构]八大排序算法总结

作者&#xff1a; 华丞臧专栏&#xff1a;【数据结构】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。推荐一款刷题网站 &#x1f449; LeetCode刷题网站 目录 一、排序的概念及其运用 1.1排…

【目的:windows下VS2017/2022配置使用opengl - 初探-创建一个空窗口】

目的&#xff1a;windows下VS2017/2022配置使用opengl - 初探-创建一个空窗口 环境&#xff1a; 系统&#xff1a;Win10 环境&#xff1a;VS2017 64bit步骤&#xff1a; windows下visualstudio下使用opengl&#xff0c;搭建配置环境并测试窗口 1、opengl库&#xff0c;vs下自…

Crack:Open Inventor 10.12.1 Fixed Bugs List 10.12

10.12.0 - 10.12.1 Open Inventor 10.12.1 Core #OIV-4245 Shapes not rendered with MultipleInstancing  #OIV-4258 Transparency issue with SoPackedColor – CAS-41256-F0S4 OivSuite.Java #OIV-4273 Memory leak with RemoteViz Java and JVM VolumeViz #OI…

CMake中add_library的使用

CMake中的add_library命令用于使用指定的源文件向项目(project)中添加库&#xff0c;其格式如下&#xff1a; add_library(<name> [STATIC | SHARED | MODULE][EXCLUDE_FROM_ALL][<source>...]) # Normal Libraries add_library(<name> OBJECT [<source&…

【Java 快速复习】垃圾回收算法 垃圾回收器

快速理解 Java 垃圾回收算法 & 垃圾回收器 先说个关系概念&#xff0c;垃圾回收的算法是逻辑概念的定义&#xff0c;用于规范垃圾回收器实现方的一些行为&#xff0c;而垃圾回收器就是实现这些算法的工具&#xff0c;这些工具大概是一系列的 C 的类以及其实现的一些对应回…