JavaSE学习笔记 详解Set集合中实现类:LinkedHashSet以及TreeSet

Set集合具有两个特点:无序性与不允许元素重复。前面我们也学习HashSet(作为Set接口的主要实现类),底层结构是哈希表。

JDK1.7:HashSet的底层数据结构为数组加链表,JDK1.8:HashSet的底层数据结构为数组加链表加红黑树。

HashSet集合的特点为元素无序,且元素是唯一的。要保证元素的唯一性,是靠元素重写hashCode()方法与equals()方法来进行保证的,如果元素不重写这两个方法,则无法保证元素的唯一性。
我们要合理的重写hashCode()方法,是为了减少调用equals()方法的次数,即减少元素碰撞的次数。

而且需要注意的是:使用HashSet方法存储数据时,在遍历时不能用普通for循环,因为Set接口中没有get()方法。可以使用增强for循环或者使用迭代器进行遍历。

public class MyTest {
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();

        hashSet.add("aaa");
        hashSet.add("aaa");
        hashSet.add("bbb");
        hashSet.add("bbb");
        hashSet.add("ccc");
        hashSet.add("ddd");

        //使用迭代器进行遍历
        Iterator<String> iterator = hashSet.iterator();
        while (iterator.hasNext()) {
            String s = iterator.next();
            System.out.println(s);
        }
        System.out.println("==============");
        //使用增强for循环进行遍历
        for (String s : hashSet) {
            System.out.println(s);

        }
    }
}

了解了HashSet之后,本节学习Set接口中另外两个实现类:LinkedHashSet以及TreeSet。


1.LinkedHashSet概述

HashSet是Set接口的典型表现,大多数情况下使用Set集合时都使用这个实现类。

HashSet和LinkedHashSet按照Hash算法来存储集合中的元素,因此具有很好的存取和查找的功能。

HashSet和LinkedHashSet集合判断两个元素是否相等的标准:两个对象hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等视为重复的元素,不能进行添加。因此,存储到HashSet和LinkedHashSet的元素要重写hashCode()方法以及equals()方法。

LinkedHashSet是HashSet的子类,它在HashSet的基础上,在结点中增加两个属性before和after维护节点的前后添加顺序。

LinkedHashSet的底层数据结构链表加哈希表,元素有序且唯一。链表保证了元素有序(这里的有序,因为在节点处增加了before和after属性维护节点的前后添加顺序),哈希表保证元素唯一。

在这里插入图片描述

import java.util.LinkedHashSet;
public class MyTest2 {
    public static void main(String[] args) {
        //LinkedHashSet的底层数据结构链表加哈希表,元素有序且唯一。
        // 链表保证了元素有序(这里的有序,因为在节点处增加了before和after属性维护节点的前后添加顺序),哈希表保证元素唯一。
        LinkedHashSet<Integer> list = new LinkedHashSet<>();
        list.add(100);
        list.add(200);
        list.add(300);
        list.add(400);
        list.add(500);
        list.add(100);
        list.add(200);
        list.add(300);
        list.add(400);
        list.add(500);

        //使用增强for循环进行遍历
        for (Integer integer : list) {
            System.out.println(integer);

        }
    }
}

运行后的结果为:
在这里插入图片描述


2.TreeSet概述

TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素的处于排序状态,同时元素也是唯一的。

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


2.1 自然排序

public TreeSet()构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。插入该 set 的所有元素都必须实现 Comparable 接口。另外,所有这些元素都必须是可互相比较的。


2.1.1 TreeSet存储常见引用数据类型

TreeSet存储Integer类型的元素并遍历
存储下列元素: 20 , 18 , 23 , 22 , 17 , 24, 19 , 18 , 24

public class MyTest {
    public static void main(String[] args) {
        ///TreeSet:底层数据结是二叉树的结构,他能保证元素的唯一性,而且还能对元素进行排序。
        TreeSet<Integer> treeset = new TreeSet<>();
        treeset.add(20);
        treeset.add(18);
        treeset.add(23);
        treeset.add(22);
        treeset.add(17);
        treeset.add(24);
        treeset.add(19);
        treeset.add(18);
        treeset.add(24);

        System.out.println("使用TreeSet排序后的集合为:");
        for (Integer integer : treeset) {
            System.out.println(integer);

        }
    }
}

运行后的结果为:

在这里插入图片描述
注意:使用TreeSet集合进行元素的自然排序,那么对元素有要求,要求这个元素 必须实现Comparable接口 否则无法进行自然排序

我们进入了Integer类,发现了实现了Compareable接口,重写了CompareTo方法,进而可以进行自然排序。
在这里插入图片描述
在这里插入图片描述


TreeSet保证元素唯一和自然排序的原理与图解:
在这里插入图片描述


2.1.2 TreeSet存储自定义类的对象进行自然排序

案例:定义一个自定义的Student类,成员属性为姓名和年龄,使用TreeSet排序方式中的自然排序方式,按照年龄大小对集合中的元素进行排序。

/*自然排序:采用空参构造,用的就是自然排序,自然排序对元素有要求,
要求元素实现 Comparable 接口,重写 compareTo这个方法
 */
public class Student implements Comparable<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 +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        //用学生的年龄进行大小进行排序,比较的逻辑由我们来写
       //return 0;
        //根据此方法返回值的正负0 来决定元素在二叉树中所存放的位置。
        //如果年龄不相等,使用年龄的相减的返回值
        int age = this.age - o.age;
        //如果年龄相等的话,并不能说明是同一个对象,还需要比较姓名
        int num=age==0? this.name.compareTo(o.name):age;
        return num;
    }
}

import java.util.TreeSet;

public class MyTest {
    public static void main(String[] args) {
        TreeSet<Student> treeset = new TreeSet<>();

        treeset.add(new Student("张三",25));
        treeset.add(new Student("张三丰",25));
        treeset.add(new Student("李四",22));
        treeset.add(new Student("王五",21));
        treeset.add(new Student("赵六",23));
        treeset.add(new Student("钱七",24));
        treeset.add(new Student("陈八",20));


        for (Student student : treeset) {
            System.out.println("年龄:"+student.getAge()+"==="+"姓名:"+student.getName());

        }
    }
}

运行后的结果为:

在这里插入图片描述


练习:定义一个自定义的Student类,成员属性为姓名和年龄,使用TreeSet排序方式中的自然排序方式,按照姓名长度的大小对集合中的元素进行排序。

public class Student implements Comparable<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 +
                '}';
    }

    @Override
    public int compareTo(Student stu) {
       //return 0;
        //按照姓名的长度来排序
        int num = this.name.length() - stu.name.length();
        //姓名长度一样了,还的比较姓名内容一样不
        int num2 = num == 0 ? this.name.compareTo(stu.name) : num;
        //姓名长度一样了,内容一样了,还得比较年龄是否一样
        int num3 = num2 == 0 ? this.age - stu.age : num2;

        return num3;

    }
}

import java.util.TreeSet;

public class MyTest {
    public static void main(String[] args) {
        TreeSet<Student> treeset = new TreeSet<>();

        treeset.add(new Student("Jack",24));
        treeset.add(new Student("Lebron",23));
        treeset.add(new Student("Lebron",23));
        treeset.add(new Student("Abubli",23));
        treeset.add(new Student("Janey",25));
        treeset.add(new Student("Tom",26));
        treeset.add(new Student("Abubli",26));

        for (Student student : treeset) {
            System.out.println("姓名"+student.getName()+"====="+"年龄"+student.getAge());

        }

    }
}

运行后的结果为:
在这里插入图片描述


2.2 比较器排序

public TreeSet(Comparator<? super E> comparator)构造一个新的空 TreeSet,它根据指定比较器进行排序。插入到该 set 的所有元素都必须能够由指定比较器进行相互比较。


2.2.1 使用比较器进行对自定义类对象进行排序

定义一个自定义的Student类,成员属性为姓名和年龄,使用TreeSet排序方式中的比较器排序方式,按照年龄大小对集合中的元素进行排序。

//自定义类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 +
                '}';
    }
}

//定义实现比较器接口的子类
import java.util.Comparator;
//使用比较器排序,需要实现Comparator接口,并且重写compare方法
public class MyComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        //根据年龄大小来进行排序
        int num = s1.getAge() - s2.getAge();
        //如果年龄相等不代表是同一个对象,还需要比较姓名的内容是否相同
        int num2=num==0?s1.getName().compareTo(s2.getName()):num;
        return num2;
    }
}

import java.util.TreeSet;
public class MyTest {
    public static void main(String[] args) {
        /*  TreeSet(Comparator < ? super E > comparator)
        构造一个新的空 TreeSet,它根据指定比较器进行排序。*/

        //如果使用有参构造,那就是比较器排序
        MyComparator myComparator = new MyComparator();
        TreeSet<Student> treeSet = new TreeSet<>(myComparator);

        treeSet.add(new Student("Jack",24));
        treeSet.add(new Student("Lebron",23));
        treeSet.add(new Student("Lebron",23));
        treeSet.add(new Student("Abubli",23));
        treeSet.add(new Student("Janey",25));
        treeSet.add(new Student("Tom",26));
        treeSet.add(new Student("Abubli",26));

        for (Student student : treeSet) {
            System.out.println("年龄:"+student.getAge()+"姓名:"+student.getName());
        }

    }
}

运行后的结果为:
在这里插入图片描述

总而言之:比较器的方式,采用有参构造,传入比较器,重写compare()方法根据此方法,返回值的正负0,来决定元素放置的左右顺序。


2.2.2 使用比较器可以对ArrayList集合和数组进行排序

1.使用Comparator比较器可以用来比较ArrayList集合中元素大小,进行排序

注意:将集合中的元素进行排序的sort方法中需要传入实现Comparator接口的子类对象

在这里插入图片描述

import java.util.ArrayList;
import java.util.Comparator;

public class MyTest2 {
    public static void main(String[] args) {
        ArrayList<Integer> list= new ArrayList<>();

        list.add(300);
        list.add(500);
        list.add(200);
        list.add(300);
        list.add(100);
        list.add(400);

        //对集合进行从大到小的排序
        //将集合中的元素进行排序的sort方法中需要传入实现Comparator接口的子类对象
        list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return b-a;
            }
        });

        System.out.println(list);


    }
}

运行后的结果为:
在这里插入图片描述


2.使用Comparator比较器可以用来比较引用数据类型数组中元素大小,进行排序

数组工具类中的sort方法可以传入实现比较器的子类对象,并重写compare方法

import java.util.Arrays;
import java.util.Comparator;

public class MyTest3 {
    public static void main(String[] args) {
        Integer[] arr = {2, 3, 6, 5, 4, 1};
        //数组工具类中的sort方法可以传入实现比较器的子类对象,并重写compare方法
        Arrays.sort(arr,new Comparator<Integer>(){
            @Override
            public int compare(Integer a, Integer b) {
                return b-a;
            }
        });

        System.out.println(Arrays.toString(arr));


    }
}

运行后的结果为:
在这里插入图片描述

3.练习:产生10个0-19之间的随机数要求随机数不能重复

编写一个程序,获取10个1至20的随机数,要求随机数不能重复。 并把最终的随机数输出到控制台。
选HashSet 可以不重复
选TreeSet 不重复还可以排序

分析:
(1)定义一个HashSet集合或者TreeSet集合
(2)产生随机数,把随机数添加到集合中
(3)判断集合的长度,使用while循环进行实现

import java.util.HashSet;
import java.util.Random;
import java.util.TreeSet;

public class MyTest {
    public static void main(String[] args) {
       /* 编写一个程序,获取10个0至19的随机数,要求随机数不能重复。
        并把最终的随机数输出到控制台。
        */

        Random random = new Random();
        TreeSet<Integer> set = new TreeSet<>();
        
        // HashSet<Integer> set = new HashSet<>();
        while(set.size()<10){
            int num = random.nextInt(20);
            set.add(num);
        }

        System.out.println(set);
    }
}


总结

本节主要介绍了Set集合的两个实现类LinkedHashSet以及TreeSet集合,其中LinkedHashSet是HashSet集合的子类。LinkedHashSet在HashSet的基础上,在结点中增加两个属性before和after维护节点的前后添加顺序。

TreeSet需要我们掌握的是两种排序方法:使用空参构造的自然排序法以及使用比较器进行比较器排序。
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值