Java学习分享【集合】Set (Collection)

本文详细介绍了Java集合框架中的Set接口,重点讲解了HashSet和TreeSet的特性、构造方法及使用场景。HashSet基于哈希表实现,不保证元素顺序,不允许重复元素;而TreeSet则采用红黑树结构,元素按特定顺序排序,同样不允许重复。

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

前几期我们总结了有关于 Collection 的相关知识,这期来总结 关于Collection接口下的 存储有序元素可重复 List<E>接口 集合 和存储无序元素不可重复 Set<E> 接口 集合。

目录

一、interface Set

1.概念介绍

1.1 简述和特点

1.2 官方相关 

2.set接口下的方法

3.HashSet 

3.1 HashSet类介绍

3.2 HashSet特点

3.3 HashSet构造方法

3.4 遍历HashSet集合

3.5 HashSet 集合存储对象

3.6 简单案例

4.TreeSet

4.1 TreeSet类介绍

4.2 TreeSet集合特点

4.3 TreeSet类构造方法

4.3 TreeSet类独有方法 

4.4  TreeSet类的使用

4.5 使用比较器将数据存储到TreeSet中

4.6  Comparable 与 Comparator的区别


一、interface Set<E>

1.概念介绍

1.1 简述和特点

Set集合是一个存储元素不能重复的集合方式,因为存储数据的时候通过判断其元素的hashCode值,哈希值不同才进行存储

特点:

        是Collection集合的子类

        不包含重复的元素的集合

        没有带索引的方法,所以不能用普通的for循环遍历

1.2 官方相关 

  • 不包含重复元素的集合。 更正式地,集合不包含一对元素e1e2 ,使得e1.equals(e2) ,并且最多一个空元素。 正如其名称所暗示的那样,这个接口模拟了数学抽象。(就是元素不重复)

  • Set接口除了继承自Collection接口的所有构造函数的合同以及add,equals和hashCode方法的合同外,还增加了其他规定,所有构造函数都必须创建一个不包含重复元素的集合

  • 注意:如果可变对象用作设置元素,则必须非常小心。 如果对象的值以影响equals比较的方式更改,而对象是集合中的元素, 则不指定集合的行为。 这种禁止的一个特殊情况是,一个集合不允许将其本身作为一个元素。(一个集合不允许将其本身作为一个元素)

  • 一些集合实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerException或ClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,尝试对不符合条件的元素的操作,其完成不会导致不合格元素插入到集合中,可能会导致异常,或者可能会成功执行该选项。(异常类型)

2.set接口下的方法

booleanadd(E e) 如果指定的元素不存在,则将其指定的元素添加(可选操作)。
booleanaddAll(Collection<? extends E> c) 将指定集合中的所有元素添加到此集合(如果尚未存在)(可选操作)。
voidclear() 从此集合中删除所有元素(可选操作)。
booleancontains(Object o) 如果此集合包含指定的元素,则返回 true
booleancontainsAll(Collection<?> c) 返回 true如果此集合包含所有指定集合的元素。
booleanequals(Object o) 将指定的对象与此集合进行比较以实现相等。
inthashCode() 返回此集合的哈希码值。
booleanisEmpty() 如果此集合不包含元素,则返回 true
Iterator<E>iterator() 返回此集合中元素的迭代器。
booleanremove(Object o) 如果存在,则从该集合中删除指定的元素(可选操作)。
booleanremoveAll(Collection<?> c) 从此集合中删除指定集合中包含的所有元素(可选操作)。
booleanretainAll(Collection<?> c) 仅保留该集合中包含在指定集合中的元素(可选操作)。
intsize() 返回此集合中的元素数(其基数)。
default Spliterator<E>spliterator() 在此集合中的元素上创建一个 Spliterator
Object[]toArray() 返回一个包含此集合中所有元素的数组。
<T> T[]toArray(T[] a) 返回一个包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。

注:Set接口中没有额外定义新的方法,使用的都是 Collection 中声明的方法

3.HashSet 

3.1 HashSet类介绍

  • public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, Serializable
  • 此类实现Set接口,由哈希表(实际为HashMap实例)支持。 对集合的迭代次序不作任何保证; 特别是,它不能保证订单在一段时间内保持不变。 这个类允许null元素。

  • 这个类提供了基本操作(add,remove,contains和size)固定的时间性能,假定哈希函数将分散的桶中正确的元素。 迭代此集合需要与HashSet实例的大小(元素数量)和后台HashMap实例(桶数)的“容量”的总和成比例的时间。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。

    请注意,此实现不同步。 如果多个线程并发访问哈希集,并且至少有一个线程修改该集合,那么它必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该集合应该使用Collections.synchronizedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:

      Set s = Collections.synchronizedSet(new HashSet(...)); 
  • 该类iterator方法返回的迭代器是故障快速的 :如果集合在迭代器创建之后的任何时间被修改,除了通过迭代器自己的remove方法之外,迭代器会抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。

3.2 HashSet特点

1、底层数据结构是哈希表

2、对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的先后顺序一致;

3、没有带索引的方法,所以不能用普通的for循环遍历, 但可以用加强for和 iterator 迭代器遍历

4、由于是Set集合,所以不包含重复元素

使用格式:

格式: Set<T>  对象名=new  HashSet<T>();  //T是数据类型

           HashSet   对象名=new   HashSet<T>();

HashSet集合要保证元素唯一性:

HashSet是通过哈希值存储元素,使得元素的不重复性。

要保证唯一性,需要在引用类型中重写hashCode()和equals()方法;

3.3 HashSet构造方法

HashSet() 构造一个新的空集合; 背景HashMap实例具有默认初始容量(16)和负载因子(0.75)。
HashSet(Collection<? extends E> c) 构造一个包含指定集合中的元素的新集合。
HashSet(int initialCapacity) 构造一个新的空集合; 背景HashMap实例具有指定的初始容量和默认负载因子(0.75)。
HashSet(int initialCapacity, float loadFactor) 构造一个新的空集合; 背景HashMap实例具有指定的初始容量和指定的负载因子。

3.4 遍历HashSet集合

public class Demo01_HashSet_method {
    public static void main(String[] args) {
        Set<Integer> list = new HashSet<>();
        list.add(100);
        list.add(2);
        list.add(22);
        list.add(1);
        list.add(10);
        System.out.println(list);
        // [1, 2, 100, 22, 10] 存储无序[***],不可重复
        // 按照哈希值进行存储
        int num = list.size();
        System.out.println(num);
        System.out.println(list.remove(33));//false
        //for增强
        for (Integer integer : list) {
            System.out.println(integer);
        }
        //迭代器
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            iterator.next();
        }
    }
}
//输出结果:
[1, 2, 100, 22, 10]
5
false
1
2
100
22
10

3.5 HashSet 集合存储对象

class Person{
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
	//重写toString方法,使描述清晰
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
	//重写equals方法 加上内容的对比
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id && Objects.equals(name, person.name);
    }
	//重写hashCode方法,根据此类返回相应的哈希值
    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
    //这是保证在equalS为true 的基础上,两个数的哈希码值也一样,这样才能确保这是同一条数据
}
public class Demo02_HashSet_method_2 {
    public static void main(String[] args) {
        Set<Person> setPeople = new HashSet<>();
        setPeople.add(new Person(1,"wangwu"));
        setPeople.add(new Person(1,"wangwu"));
        setPeople.add(new Person(1,"wangwu"));
        System.out.println(setPeople);
    }
}
//输出结果:[Person{id=1, name='wangwu'}]

3.6 简单案例

/*
创建Set接口的实现类,添加10个以上的元素,
通过foreach遍历此集合元素
*/
public class Test03 {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();
        //100以内的随机数选10个
        set = randomNumSet(set,13,50);
        for (Integer integer : set) {
            System.out.println("元素:" + integer);
        }
//        Iterator<Integer> iterator = set.iterator();
//        while (iterator.hasNext()){
//            System.out.println("元素:" + iterator.next());
//        }
    }
    //
    public static Set<Integer> randomNumSet(Set<Integer> set,int num,int bound){
        int count = 0;
        while (count < num){
            Random random = new Random();
            int n = random.nextInt(bound);
            if (!set.contains(n)){
                set.add(n);
                count++;
            }
        }
        return set;
    }
}

4.TreeSet

存储无序,元素按一定顺序排序,元素不重叠,底层是红黑树

4.1 TreeSet类介绍

  •  
    public class TreeSet<E>
    extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, Serializable
  • 需要注意的是由一组(无论是否提供了明确的比较器)保持的顺序必须与equals一致 ,如果它是要正确实现Set接口。 (参见ComparableComparator一致的精确定义与equals)。这是因为该Set接口在来定义equals的操作,但一个TreeSet例如使用其执行所有元件比较compareTo (或compare )方法,于是两个通过该方法认为相等的元素从集合的角度来看是相等的。 集合的行为明确定义的,即使其排序与equals不一致; 它只是没有遵守Set界面的总体合同。

4.2 TreeSet集合特点

1、元素有序:这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方法取决于构造方法。

        TreeSet(): 根据其元素的自然排序进行排序;

        TreeSet (Comparator comparator):根据指定的比较器进行排序

2、没有索引的方法:所以不能用普通的for循环遍历,可以用加强for或迭代器iterator

3、由于是Set集合,所以不包含重复元素

4.3 TreeSet类构造方法

TreeSet() 构造一个新的,空的树组,根据其元素的自然排序进行排序。
TreeSet(Collection<? extends E> c) 构造一个包含指定集合中的元素的新树集,根据其元素的 自然排序进行排序
TreeSet(Comparator<? super E> comparator) 构造一个新的,空的树集,根据指定的比较器进行排序。
TreeSet(SortedSet<E> s) 构造一个包含相同元素的新树,并使用与指定排序集相同的顺序。

4.3 TreeSet类独有方法 

常用的不多

Eceiling(E e) 返回此集合中最小元素大于或等于给定元素,如果没有此元素,则返回 null
Comparator<? super E>comparator() 返回用于对该集合中的元素进行排序的比较器,或null,如果此集合使用其元素的natural ordering
Iterator<E>descendingIterator() 以降序返回该集合中的元素的迭代器。
NavigableSet<E>descendingSet() 返回此集合中包含的元素的反向排序视图。
Efirst() 返回此集合中当前的第一个(最低)元素。
Efloor(E e) 返回此集合中最大的元素小于或等于给定元素,如果没有这样的元素,则返回 null
SortedSet<E>headSet(E toElement) 返回此集合的部分的视图,其元素严格小于 toElement
NavigableSet<E>headSet(E toElement, boolean inclusive) 返回该集合的部分的视图,其元素小于(或等于,如果 inclusive为真) toElement
Ehigher(E e) 返回严格大于给定元素的该集合中的最小元素,如果没有此元素则返回 null
Elast() 返回此集合中当前的最后(最高)元素。
Elower(E e) 返回这个集合中最大的元素严格小于给定的元素,如果没有这样的元素,则返回 null
EpollFirst() 检索并删除第一个(最低)元素,或返回 null如果该集合为空。
EpollLast() 检索并删除最后一个(最高)元素,如果此集合为空,则返回 null
booleanremove(Object o) 如果存在,则从该集合中删除指定的元素。
intsize() 返回此集合中的元素数(其基数)。
Spliterator<E>spliterator() 在此集合中的元素上创建late-binding故障切换 Spliterator
NavigableSet<E>subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) 返回该集合的部分的视图,其元素的范围从 fromElementtoElement
SortedSet<E>subSet(E fromElement, E toElement) 返回此集合的部分的视图,其元素的范围从 fromElement (含)到 toElement ,排他。
SortedSet<E>tailSet(E fromElement) 返回此组件的元素大于或等于 fromElement的部分的视图。
NavigableSet<E>tailSet(E fromElement, boolean inclusive) 返回此集合的部分的视图,其元素大于(或等于,如果 inclusive为真) fromElement

4.4  TreeSet类的使用

4.4.1 创建对象格式:

格式: Set<T>  集合名=new  TreeSet<T>();  //  T表示数据类型

                TreeSet<T>  集合名=new TreeSet<T>();

4.4.2 存一般数据:  

public class Demo01 {
    public static void main(String[] args) {
        //TreeSet在存储数据的时候 会排序
        Set<Integer> set = new TreeSet<>();
        set.add(12);
        set.add(15);
        set.add(22);
        set.add(35);
        set.add(1);
        System.out.println(set);//[1, 12, 15, 22, 35]

        Set<String> set1 = new TreeSet<>();
        set1.add("狗蛋");
        set1.add("狗剩");
        set1.add("a");
        set1.add("cd");
        System.out.println(set1);//按照字典顺序进行排序 (自然排序)
    }
}
//输出结果:
[1, 12, 15, 22, 35]
[a, cd, 狗剩, 狗蛋]

 4.4.3 TreeSet集合存储对象:

如果想要在TreeSet集合中添加对象的话,一定要去实现Comparable这个接口

自然排序Comparable的使用:

1、用TreeSet 集合存储定义对象,无参构造方法使用的是自然排序对元素进行排序的;

2、自然排序,就是让元素元素所属的类实现Comparable接口,重写compareTo(T o)方法

3、重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写。

compareTo(T o)抽象方法:  

`int``compareTo(T o)`将此对象与指定的对象进行比较以进行排序。

//将此对象与指定的对象进行比较以进行排序。 返回一个负整数,零或正整数,因为该对象小于,等于或大于指定对象。

返回正数:往二叉树的右边添加
返回负数:往二叉树的左边添加
返回 0 : 说明重复,不添加

代码示例1:

class Student implements Comparable<Student>{ //!!!一定要实现Comparable这个接口
    String name;
    int age;

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

    @Override
    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);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
	//重写compareTo方法
    @Override
    public int compareTo(Student o) {
        System.out.println("执行比较方法");//比较次数无规律
        return this.age - o.age; // 比较的是 int age 属性
    }
}
public class Demo02 {
    public static void main(String[] args) {
        Set<Student> set = new TreeSet<>();

        set.add(new Student("wf",1));
        //未实现接口前
        //ClassCastException 类转换异常
        //证明Student 转换不了Comparable
        set.add(new Student("tf",2));
        set.add(new Student("lf",3));

        System.out.println(set);
    }
}

代码示例2:

class Employee implements Comparable<Employee>{
    String name;
    int age;
    int weight;

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

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", weight=" + weight +
                '}';
    }

    @Override
    public int compareTo(Employee o) {
        //先按照年龄比,如果年龄相等,则比较体重
        int num1 = this.age - o.age;
        if (num1 == 0){
            int num2 = this.weight - o.weight;
            if (num2 == 0){
                int num3 = this.name.hashCode()-o.name.hashCode();
                return num3;
            }else return num2;
        }
        return num1;
    }
}
public class Demo02_TreeSet_2 {
    public static void main(String[] args) {
        Set<Employee> set = new TreeSet<>();
        set.add(new Employee("茅台",48,78));
        set.add(new Employee("蘑菇头",22,56));
        set.add(new Employee("闰土",38,48));
        set.add(new Employee("朱晓明",50,66));
        set.add(new Employee("国魏",22,74));
        set.add(new Employee("王宝钏",48,65));
        set.add(new Employee("贾宝玉",48,56));
        set.add(new Employee("林黛玉",50,70));
        set.add(new Employee("黄天霸",16,65));
        set.add(new Employee("流川枫",43,82));
        set.add(new Employee("流川云",43,82));
        //System.out.println(set);
        for (Employee employee : set) {
            System.out.println(employee);
        }
    }
}
//输出结果:
/*
Employee{name='黄天霸', age=16, weight=65}
Employee{name='蘑菇头', age=22, weight=56}
Employee{name='国魏', age=22, weight=74}
Employee{name='闰土', age=38, weight=48}
Employee{name='流川云', age=43, weight=82}
Employee{name='流川枫', age=43, weight=82}
Employee{name='贾宝玉', age=48, weight=56}
Employee{name='王宝钏', age=48, weight=65}
Employee{name='茅台', age=48, weight=78}
Employee{name='朱晓明', age=50, weight=66}
Employee{name='林黛玉', age=50, weight=70}
*/

 

4.5 使用比较器将数据存储到TreeSet中

class Student{
    String name;
    int age;

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
//实现比较器接口,重写compare方法
class MyComparator implements Comparator<Student>{

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age; // 按照学生年龄排序
    }
}
public class Demo01 {
    public static void main(String[] args) {
        //如果想要使用比较器的写法,必须在newTreeSet 时侯加上比较器比较对象
        Set<Student> students = new TreeSet<>(new MyComparator());
        students.add(new Student("望天",12));
        students.add(new Student("附件",46));
        students.add(new Student("发放",54));
        students.add(new Student("发我",22));
        students.add(new Student("望热",12));
        students.add(new Student("茅蹲",23));
        System.out.println(students);
    }
}
//输出结果:
[Student{name='望天', age=12}, Student{name='发我', age=22}, Student{name='茅蹲', age=23}, Student{name='附件', age=46}, Student{name='发放', age=54}]

4.6  Comparable 与 Comparator的区别

Comparable 和 Comparator 都是接口,均为比较器

Comparable相当于“内比较器”,而Comparator相当于“外比较器”

实现Comparable的类,该类就具有自身比较的功能;

Comparator的实现,是一个外部比较工具器

 

注意:

        Comparable 和 Comparator 同时实现时,则以Comparator为主

Comparable compare To方法:

Comparator compare方法 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值