Java中的Set集合

本文详细介绍了Java中的Set集合,包括HashSet、LinkedHashSet和TreeSet的特点和使用。HashSet基于哈希表,不保证元素顺序,但保证元素唯一性。LinkedHashSet在保证元素唯一的同时,按插入顺序遍历。TreeSet通过比较器或自然排序保持元素有序。此外,文章还讲解了哈希值、比较器和Comparable接口在保证集合元素唯一性和排序中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

3.1Set集合概述和特点

public interface Set extends Collection
不包含重复元素的集合。
Set是一个接口,接口不能直接实例化,所以我们在创建Set对象的时候,使用Set接口的实现类HashSet来创建Set对象。

import java.util.HashSet;
import java.util.Set;

public class SetDemo01 {
    public static void main(String[] args) {
    //Set是一个接口,不能直接实例化,所以创建对象时创建实现类HashSet的对象
        Set<String> set=new HashSet<String>();
        set.add("hello");
        set.add("world");
        set.add("set");
        set.add("collection");
        set.add("world");//这个值添加不进去,因为set是不重复的

        for(String s:set){
            System.out.println(s);//输出是无序的
        }
    }
}

输出结果:

world
set
hello
collection

3.2 哈希值

哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Obejct类中有一个方法可以获取对象的哈希值(hashCode())

1、同一个对象多次调用hashCode()获取的哈希值是一样的
2、默认情况下(使用Collection中的hashCode()),不同对象的哈希值是不一样的。即使不同对象的值一样,哈希值也不一样。
如果对hashCode()进行了重写,那么哈希值可能会相同。

3.3HashSet集合概述和特点

public class HashSet(E) extends AbstractSet
implements Set,Cloneable,Serailizable
该类实现Set集合,由哈希表实际为HashMap实例支持。对集合的迭代顺序不做任何保证。特别是它不能保证订单在一段时间内保持不变。这个类允许null元素。
HashSet集合特点:
1、底层数据结构是哈希表;
2、对集合的迭代顺序不做任何保证,也就是说不保证存储和取出的元素顺序一致;
3、没有带索引的方法,所以不能用普通for循环遍历;
4、由于是Set集合,所以是不包含重复元素的集合。

3.3.1HashSet集合存储字符串并遍历

这里需要注意的是:HashSet底层是Set集合,所以也是不允许重复元素的

import java.util.HashSet;

public class HashSetDemo01 {
    public static void main(String[] args) {
        HashSet<String> hashSet=new HashSet<String>();
        hashSet.add("h");
        hashSet.add("e");
        hashSet.add("l");
        hashSet.add("l");
        hashSet.add("o");

        for(String s:hashSet){
            System.out.println(s);
        }
    }
}

输出结果:

e
h
l
o

3.4HashSet保证集合元素唯一性的源码分析

HashSet添加元素时,首先会去调用元素的**hashCode()计算元素的哈希值。hash值和元素的hashCode()**方法相关。
如果hash表未被初始化,就对其初始化;
再根据对象的hash值计算对象的存储位置,如果该位置没有元素,就存储元素;
存入元素和以前的元素比较哈希值
如果hash值不同,会继续向下执行,把元素添加到集合;
如果hash值相同,会调用对象的equal()方法比较;
调用equal方法返回true时,说明元素重复,不存储;
调用equal方法返回false时,会继续执行,添加元素。

HashCode集合存储元素:
需要保证元素唯一性,需要重写hashCode()和equals()

3.5常见数据结构之哈希表

哈希表:
JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组;
JDK8以后,在长度比较长的时候,底层实现了优化。
构造方法:
HashSet():构造一个新的空集合;背景HashMap()实例具有默认初始容量(16)和负载因子(0.75)

3.5.1案例:HashSet集合存储学生对象并遍历

需求:创建一个存储对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认为是同一个对象。
思路:
1、定义学生类
2、创建HashSet集合对象
3、创建学生对象
4、将学生对象添加到集合中
5、循环遍历集合
6、在学生类中重新两个方法
hashCode()和equals()->可以自动生成
如果不重写这两个方法,那么循环输出的结果会重复

package daily_collection;

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

    public Student(){
    }

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

    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;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}


package daily_collection;

import java.util.HashSet;

public class HashSetDemo02 {
    public static void main(String[] args) {
        HashSet<Student> hs=new HashSet<Student>();
        Student s1=new Student("ALL",1);
        Student s2=new Student("IS",2);
        Student s3=new Student("WELL",3);
        Student s4=new Student("WELL",3);

        hs.add(s1);
        hs.add(s2);
        hs.add(s3);
        hs.add(s4);

        for(Student s:hs){
            System.out.println(s.getName()+"-"+s.getAge());
        }

    }
}

3.6LinkedHashSet集合概述和特点

public LinkedHashSet extends HashSet
implements Set,Cloneable,Serializable

LinkedHashSet是哈希表和链表实现Set接口,具有可预测的迭代次序。该实现与HashSet的不同之处在于它保持双向链表的所有条目。

LinkedHashSet集合特点:
1、哈希表和链表实现Set接口,具有可预测的迭代次序;
2、由链表保证元素有序,也就是说元素的存储和取出顺序是一致的;
3、由哈希表保证元素唯一,也就是说没有重复的元素。

3.6.1LinkedHashSet集合存储元素并遍历

import java.util.LinkedHashSet;

public class LinkedHashSetDemo01 {
    public static void main(String[] args) {
        LinkedHashSet<String> lhs=new LinkedHashSet<String>();
        lhs.add("h");
        lhs.add("e");
        lhs.add("l");
        lhs.add("l");
        lhs.add("o");

        for(String s:lhs){
            System.out.println(s);
        }
    }
}

3.7TreeSet集合概述和特点

public class TreeSet extends AbstractSet
implements NavigableSet,Cloneable,Serializable.
TreeSet集合特点:
1、元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方法取决于构造方法
TreeSet():根据元素的自然排序进行排序;
TreeSet(Comparator comparator):根据指定的比较器进行排序。
2、没有带索引的方法,所以不能使用普通for循环遍历
3、由于是Set()集合,所以不包含重复元素的集合

3.7.1TreeSet集合存储整数并遍历

使用无参构造进行自然排序:

import java.util.TreeSet;

public class TreeSetDemo01 {
    public static void main(String[] args) {
        TreeSet<Integer> treeSet=new TreeSet<Integer>();
        treeSet.add(2);
        treeSet.add(13);
        treeSet.add(7);
        treeSet.add(5);
        treeSet.add(5);//treeSet也是不重复的,此元素不会被添加

        for (Integer i:treeSet){
            System.out.println(i);
        }
    }
}

3.8自然排序Comparable的使用

需求:存储学生对象并遍历,创建TreeSet集合使用无参构造方法;
要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序。

 @Override
    public int compareTo(Student o) {
//        return 0;//返回0代表元素是重复的
//        return 1;//返回1代表元素是不重复的,按照存储顺序输出
//        return -1;//返回-1,按照降序存储
        //按照年龄的降序排列
        int num = this.age-o.age;
        int num2 = num==0?this.name.compareTo(o.name):num;//年龄相等时,按姓名比较
        return num;
    }

结论:
1、用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的;
2、自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(To)方法;
3、重写方法时,一定要注意排序规则,必须按照要求的主要条件和次要条件来写。

3.9比较器排序Comparator的使用

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

package daily_comparator;

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

    public Student(){

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

    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;
    }
}

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

public class TreeSetDemo01 {
    public static void main(String[] args) {
        TreeSet<Student> treeSet = new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int num = o2.getAge() - o1.getAge();
                int num2 = num == 0 ? o1.getName().compareTo(o2.getName()) : num;
                return num2;
            }
        });
        Student s1 = new Student("xishi", 28);
        Student s2 = new Student("diaochan", 12);
        Student s3 = new Student("wangzhaojun", 43);
        Student s4 = new Student("yangyuhuan", 33);

        treeSet.add(s1);
        treeSet.add(s2);
        treeSet.add(s3);
        treeSet.add(s4);

        for (Student s : treeSet) {
            System.out.println(s.getName() + "," + s.getAge());
        }
    }
}

结论:
1、用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的;
2、比较器排序就是让构造方法接受Comparator的实现类对象,重写compareTo(T o1,T o2)方法
3、重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写。

3.9.1案例:成绩排序

需求:用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩),并遍历该集合
要求:按照总分从高到底出现
思路:
1、定义学生类
2、创建TreeSet集合对象,通过比较器排序进行排序
3、创建学生对象
4、将学生对象添加到集合中
5、遍历集合
note:
这里需要注意的是,根据主要条件判断总分是否大于小于,没有包含等于的情况;所以有次要条件,总分相等时我们按照语文成绩的高低排序;其次还有如果语文成绩也相等时,那么还需要比较姓名,因为不同的人可能总分和语文成绩都相等,如果不再附加比较姓名的次要条件时,那么各科成绩相等但姓名不一致时,这种对象的数据不会参与排序。

package daily_comparator;

public class Student {
    private String name;
    private int chinese;
    private int math;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getChinese() {
        return chinese;
    }

    public void setChinese(int chinese) {
        this.chinese = chinese;
    }

    public int getMath() {
        return math;
    }

    public void setMath(int math) {
        this.math = math;
    }

    public int getSum(){
        return this.chinese+this.math;
    }
}

package daily_comparator;

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

public class TreeSetDemo02 {
    public static void main(String[] args) {
        TreeSet<Student> treeSet=new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int num=o2.getSum()-o1.getSum();
                int num2=num==0?o1.getChinese()-o2.getChinese():num;
                int num3=num2==0?o1.getName().compareTo(o2.getName()):num2;
                return num3;
            }
        });

        Student s1=new Student("Anna",87,99);
        Student s2=new Student("Tom",95,90);
        Student s3=new Student("Luna",84,88);
        Student s4=new Student("John",93,92);
        Student s5=new Student("John1",93,92);
        treeSet.add(s1);
        treeSet.add(s2);
        treeSet.add(s3);
        treeSet.add(s4);
        treeSet.add(s5);


        for(Student s:treeSet){
            System.out.println(s.getName()+"-"+s.getSum());
        }

    }
}


3.9.2案例:不重复随机数

需求:编写一个程序,获取10个1-20之间的随机数,要求随机数不重复,并在控制台输出。
思路:
1、创建Set集合对象;
2、创建随机数对象;
3、判断集合的长度是都小于10;
是:产生一个随机数,添加到集合
回到3继续
4、遍历集合。

package daily_comparator;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class TreeSetDemo03 {

    public static void main(String[] args) {
        Set<Integer> set=new HashSet<Integer>(){};
        Random r =new Random();

        while(set.size()<10){
            int n = r.nextInt(20) + 1;
            set.add(n);
        }
        for(Integer i:set){
            System.out.println(i);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值