20--Set集合

2023-12-16 16:47:17

1、Set集合

1.1 Set集合概述

java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。Set集合有多个子类:java.util.HashSet、java.util.LinkedHashSet、java.util.TreeSet

1.2 Set集合的特点

Set集合中的元素不可重复

Set集合没有索引

总结: 不可重复性、无序性

2、HashSet集合

2.1 HashSet概述

HashSet 是 Set 接口的主要实现类,大多数时候使用 Set 集合时都使用这个实现类。

HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存储、查找、删除性能。

HashSet 具有以下特点:

  • 不能保证元素的排列顺序
  • HashSet 不是线程安全的
  • 集合元素可以是 null

HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法得到的哈希值相等,并且两个对象的 equals()方法返回值为true。

对于存放在Set容器中的对象,对应的类一定要重写hashCode()和equals(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

HashSet集合中元素的无序性,不等同于随机性。这里的无序性与元素的添加位置有关。具体来说:我们在添加每一个元素到数组中时,具体的存储位置是由元素的hashCode()调用后返回的hash值决定的。导致在数组中每个元素不是依次紧密存放的,表现出一定的无序性。

2.2 HashSet集合的特点

HashSet集合中的元素不可重复

HashSet集合没有索引

HashSet集合是无序的(存储元素的顺序与取出元素顺序可能不一致)

总结: 不可重复性、无序性

2.3 HashSet常用方法

package com.suyv.set;

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

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:46
*@Description: HashSet常用方法
*/
public class HashSetDemo01 {
    public static void main(String[] args) {
        Set set = new HashSet();

        // 添加数据
        set.add("张三");
        set.add("lisi");
        set.add(null);
        set.add(new Date());
        set.add(10);

        // 遍历set
        for (Object obj : set) {
            System.out.println(obj);
        }

        // 删除数据
        set.remove("lisi");
        System.out.println(set);    // [null, Sat Dec 16 11:52:29 CST 2023, 张三, 10]

        // 获取长度
        System.out.println(set.size()); // 4

        // 判断非空
        System.out.println(set.isEmpty());  // false

        // 清空数据
        set.clear();
        System.out.println(set);    // []
    }
}

如何保证Hashset集合唯一?

底层依赖 两个方法:hashCode()和equals()。

????????步骤:

????????????????首先比较哈希值

????????????????如果相同,继续走,比较地址值或者走equals()

????????????????如果不同,就直接添加到集合中

????????按照方法的步骤来说:

????????????????先看hashCode()值是否相同

????????????????相同:继续走equals()方法

????????????????????????返回true: 说明元素重复,就不添加

????????????????????????返回false:说明元素不重复,就添加到集合

????????????????不同:就直接把元素添加到集合

如果类没有重写这两个方法,默认使用的Object()。一般来说一样。

而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。

2.4 HashSet存储自定义类型元素

2.4.1 案例1

定义Student类--不重写hashCode()和equals()

package com.suyv.set;

import java.util.Objects;

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:55
*@Description: 实体类:Student
*/
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 String toString() {
        return "Student{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
    }
}

定义测试类

package com.suyv.set;

import java.util.HashSet;

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:59
*@Description: HashSet测试添加自定义实体类对象
*/
public class HashSetDemo02 {
    public static void main(String[] args) {
        //创建集合对象   该集合中存储 Student类型对象
        HashSet<Student> stuSet = new HashSet<Student>();
        //存储
        stuSet.add(new Student("于谦", 43));
        stuSet.add(new Student("于谦", 43));
        stuSet.add(new Student("郭麒麟", 23));
        stuSet.add(new Student("郭麒麟", 23));

        for (Student stu2 : stuSet) {
            System.out.println(stu2);
        }
    }
}

结果:

2.4.2 案例2

定义Student类--重写hashCode()和equals()

package com.suyv.set;

import java.util.Objects;

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:55
*@Description: 实体类:Student
*/
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 String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }


    //不需要你手动重写Object  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 && Objects.equals(name, student.name);
    }

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

}

定义测试类

package com.suyv.set;

import java.util.HashSet;

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 11:59
*@Description: HashSet测试添加自定义实体类对象
*/
public class HashSetDemo02 {
    public static void main(String[] args) {
        //创建集合对象   该集合中存储 Student类型对象
        HashSet<Student> stuSet = new HashSet<Student>();
        //存储
        stuSet.add(new Student("于谦", 43));
        stuSet.add(new Student("于谦", 43));
        stuSet.add(new Student("郭麒麟", 23));
        stuSet.add(new Student("郭麒麟", 23));

        for (Student stu2 : stuSet) {
            System.out.println(stu2);
        }
    }
}

结果:

2.5 HashSet集合存储数据的结构

JDK的版本不同,HashSet集合的数据结构有所不同:

JDK8之前:数组+链表

JDK8之后:数组+链表+红黑树

以上数据结构我们称之为是哈希表

2.5.1 什么是哈希表

在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。

3、LinkedHashSet

3.1 什么是LinkedHashSet

我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?

在HashSet下面有一个子类java.util.LinkedHashSet,它是 链表 和 哈希表 组合的一个数据存储结构

3.2 LinkedHashSet集合的特点

LinkedHashSet 是 HashSet 的子类,不允许集合元素重复。

LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以添加顺序保存的。

LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。

3.3 LinkedHashSet常用方法

package com.suyv.set;

import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 12:11
*@Description: LinkedHashSet常用方法
*/
public class LinkedHashSetDemo01 {
    public static void main(String[] args) {
        Set set = new LinkedHashSet();

        // 添加数据
        set.add("张三");
        set.add("lisi");
        set.add(null);
        set.add(new Date());
        set.add(10);

        // 遍历set
        for (Object obj : set) {
            System.out.println(obj);
        }

        // 删除数据
        set.remove("lisi");
        System.out.println(set);    // [张三, null, Sat Dec 16 12:21:11 CST 2023, 10]

        // 获取长度
        System.out.println(set.size()); // 4

        // 判断非空
        System.out.println(set.isEmpty());  // false

        // 清空数据
        set.clear();
        System.out.println(set);    // []
    }
}

4、TreeSet

使用元素的自然排序对元素进行排序或者根据创建set时提供的Comparable排序(定制排序)具体取决于你用的构造方法

4.1 TreeSet概述

TreeSet 是 SortedSet 接口的实现类,TreeSet 可以按照添加的元素的指定的属性的大小顺序进行遍历

TreeSet底层使用红黑树结构存储数据

新增的方法如下: (了解)

  • Comparator comparator()
  • Object first()
  • Object last()
  • Object lower(Object e)
  • Object higher(Object e)
  • SortedSet subSet(fromElement, toElement)
  • SortedSet headSet(toElement)
  • SortedSet tailSet(fromElement)

TreeSet特点:不允许重复、实现排序(自然排序或定制排序)

TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序

  • 自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
    • 如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。
    • 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。
  • 定制排序:如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。
    • 利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
    • 要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。

因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象

对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 或compare(Object o1,Object o2)方法比较返回值。返回值为0,则认为两个对象相等。

4.1 TreeSet自然排序

4.1.1 String类的自然排序

package com.suyv.set;

import org.junit.Test;

import java.util.Set;
import java.util.TreeSet;

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 12:30
*@Description: TreeSet自然排序--String
*/
public class TreeSetDemo01 {

    @Test
    public void Test01(){
        Set set = new TreeSet();

        set.add("AA");
        set.add("BB");
        set.add("CC");
        set.add("DD");
        //set.add(123);  //报ClassCastException的异常

        System.out.println(set);    // [AA, BB, CC, DD]
    }
}

4.1.2 自定义类的自然排序

案例1

自定义实体类:User

package com.suyv.set;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 13:54
*@Description: 实体类User
*/
public class User implements Comparable{
    private String name;
    private int age;

    public User() {
    }

    public User(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 String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }


    // 比如:按照年龄从小到大排序
    @Override
    public int compareTo(Object o) {
        if(this == o){
            return 0;
        }

        if(o instanceof User){
            User u = (User)o;
            return this.age - u.age;
        }

        throw new RuntimeException("类型不匹配");
    }
}

TreeSet测试类

package com.suyv.set;

import java.util.Iterator;
import java.util.TreeSet;

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 13:57
*@Description: TreeSet自然排序的使用
*/
public class TreeSetDemo02 {

    public static void main(String[] args) {
        TreeSet set = new TreeSet();

        set.add(new User("Tom",12));
        set.add(new User("Rose",23));
        set.add(new User("Jerry",2));
        set.add(new User("Eric",18));
        set.add(new User("Tommy",44));
        set.add(new User("Jim",23));
        set.add(new User("Maria",18));
        // set.add("Tom");

        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        System.out.println(set.contains(new User("Jack", 23))); // true
    }
}
案例2

自定义实体类:User

package com.suyv.set;
/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 13:54
*@Description: 实体类User
*/
public class User implements Comparable{
    private String name;
    private int age;

    public User() {
    }

    public User(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 String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    // 比如:先比较年龄从小到大排列,如果年龄相同,则继续比较姓名,从大到小
    @Override
    public int compareTo(Object o) {
        if(this == o){
            return 0;
        }

        if(o instanceof User){
            User u = (User)o;
            int value = this.age - u.age;
            if(value != 0){
                return value;
            }
            return -this.name.compareTo(u.name);
        }

        throw new RuntimeException("类型不匹配");
    }
}

TreeSet测试类

package com.suyv.set;

import java.util.Iterator;
import java.util.TreeSet;

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 13:57
*@Description: TreeSet自然排序的使用
*/
public class TreeSetDemo02 {

    public static void main(String[] args) {
        TreeSet set = new TreeSet();

        set.add(new User("Tom",12));
        set.add(new User("Rose",23));
        set.add(new User("Jerry",2));
        set.add(new User("Eric",18));
        set.add(new User("Tommy",44));
        set.add(new User("Jim",23));
        set.add(new User("Maria",18));
        // set.add("Tom");

        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        System.out.println(set.contains(new User("Jack", 23))); // false
        
    }
}

4.2 TreeSet定制排序

Comparator 可以看成一个外部比较器,好处不用修改原代码直接实现

package com.suyv.set;

import org.junit.Test;

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

/**
*@Author: 憨憨浩浩
*@CreateTime: 2023-12-16 14:55
*@Description: TreeSet定制排序
*/
public class TreeSetDemo03 {
    
    @Test
    public void Test01(){
        Comparator comparator = new Comparator() {
            /*
             * 按照姓名从小到大排列,如果姓名相同,继续比较age,按照从大到小排列
             * */
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;

                    int value = u1.getName().compareTo(u2.getName());
                    if(value != 0){
                        return value;
                    }
                    return -(u1.getAge() - u2.getAge());
                }
                throw new RuntimeException("类型不匹配");
            }
        };

        TreeSet set = new TreeSet(comparator);

        User u1 = new User("Tom",23);
        User u2 = new User("Jerry",43);
        User u3 = new User("Rose",13);
        User u4 = new User("Jack",23);
        User u5 = new User("Tony",33);
        User u6 = new User("Tom",33);

        set.add(u1);
        set.add(u2);
        set.add(u3);
        set.add(u4);
        set.add(u5);
        set.add(u6);

        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

Comparable<T> 内部比较器,需要修改原代码,不符合OCP原则

重写方法: public int compareTo(T t)

Comparator 可以看成一个外部比较器,好处不用修改原代码直接实现

重写方法: public int compare(Ojbect s1, Ojbect s2)

返回值类型:int 等于0 表示相等 大于0表示升序 小于0表示是降序

文章来源:https://blog.csdn.net/weixin_46612641/article/details/135033333
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。