Java学习笔记===》18.set系列集合

set系列集合

1.Set系列集合的特点

(1)无序:

​ 存取顺序不一致

(2)不重复:

​ 可以去除重复

(3)无索引:

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

(4)Set接口中的方法基本上与Collection的API一致

​ 添加的元素是有序(这里的有序是存和取的顺序是一样的)、可重复、有索引

方法名称说明
punlic boolean add(E e)把给定的对象添加到当前集合当中
public void clear ()清空集合中所有的元素
public booolean remove(E e)把给定的对象再当前集合中删除
public bolean cintains(Object obj)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()返回集合中元素的个数/集合的长度

2.Set集合的实现类

(1)HashSet:

①特点:

​ 无序、不重复、五索引

(2)LinkedHashSet:

①特点:

​ 有序、不重复、五索引

(3)TreeSet:

①特点:

​ 可排序、不重复、五索引

package com_06Gather._01gatherStructure._03SetTest;

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

public class SetTest01 {
    public static void main(String[] args) {
/*
利用set系列的集合,添加字符串,并使用多种方式遍历
增强for
Lambda表达式
 */

        //1.创建一个Set集合
        Set<String> list = new HashSet<>();

        //2.添加元素
        //如果当前元素是第一次添加,那么可以添加成功 ,返回true
        //如果当前元素是第二次添加,那么可以添加失败 ,返回false
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");

        //Set系列集合不允许元素重复
        boolean r1 = list.add("eee");//true;
        boolean r2 = list.add("eee");//false;

        //打印集合,得到的元素是无序的
        System.out.println(list); //[aaa, ccc, bbb, eee, ddd]


        //1.利用迭代器进行遍历
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String str1 = it.next();
            /*if("eee".equals(str1)){
                list.remove(str1);
            }*/
            System.out.print(str1+", ");
        }

        System.out.println("\n"+"==========================================");

        //2.增强for遍历
        for (String str2 : list) {
            System.out.print(str2+", ");
        }

        System.out.println("\n"+"==========================================");

        //3.利用Lambda表达式进行遍历
        list.forEach(str3-> System.out.print(str3+", "));
    }
}

3.HashSet底层原理

★HashSet集合底层采用哈希表存储数据

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

(1)哈希表的组成

​ ★JDK8以前:数组+链表

​ ★JDK8开始:数组+链表+红黑树

①哈希值:对象的整数表现形式

​ ★根据hashcode方法计算出来的int类型的整数

​ ★该方法定义在Object类中,所有的对象都可以调用,默认使用地址值进行计算

​ ★一般情况下,会重写hashcode方法,利用对象内部的属性值计算哈希值

②对象的哈希值特点

​ ★如果没有重写hashcode方法,不同对象计算出的哈希值是不同的

​ ★如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的

​ ★在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)

public class _02HashSetTest01 {
    public static void main(String[] args) {

        //1.创建对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("zhangsan",23);

        //2.如果没有重写hashcode方法,不同对象计算出的哈希值是不同的
        System.out.println(s1.hashCode()); //1329552164
        System.out.println(s2.hashCode()); //363771819

        //3.如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
        System.out.println(s1.hashCode()); //-1461067292
        System.out.println(s2.hashCode()); //-1461067292

        //4.在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
        System.out.println("abc".hashCode()); //96354
        System.out.println("acD".hashCode()); //96354
    }
}

(2)HashSet 在JDK8以前及以后的底层原理

​ ① HashSet< String> hm = new HashSet<>(); 当创建HashSet对象时,在底层,创建了一个默认长度为16,默认**加载因子为0.75**的数组,数组名为table,数组里面的数据为默认初始化的null;

​ **加载因子:**也就是底层数组的扩容时机,当 数组长度 16 x 加载因子0.75 = 12 的时候,数组就会扩容为原来的2倍;

​ ②根据元素的哈希值跟数组的长度计算出因存入的位置。公式: int index = ( 数组长度-1 ) & 哈希值

​ ③判断当前位置是否为null,如果是null直接存入

​ ④如果不是null,表示有元素,则调用 equals方法比较属性值

​ ⑤如果步骤④的属性值一样,那么直接舍弃(保证数据的唯一),不存入数组,如果不一样,存入数组,形成链表

​ ⑥在JDK8以前:新的元素存入数组,老元素挂在新元素下面

​ ⑦在JDK8及以后:新的元素直接挂在老元素下面

​ ⑧但是当**链表的长度大于等于 8 ,且数组的长度大于等于 64的时候,这个数组会自动转为红黑树**

​ ⑨如果集合中存储的是自定义对象,必须要重写**hashCodeequals**方法

(3)HashSet的三个问题

①HashSet为什么存和取得顺序不一样

​ 当HashSet在遍历的时候,或按照顺序遍历,也就是说在 底层数组的0索引上有链表,1索引上有红黑树,会先遍历0索引上的数据和0索引上的链表,再遍历1索引上的元素和1索引上的红黑树,所以说村和取的数据据不一样

②HashSet为什么没有索引

​ 如果说在底层数组的0索引上有链表,1索引上有红黑树,那么链表上的数据会公用0索引,而1索引上的红黑树的所有元素会公用1索引,造成哈希碰撞,所以HashSet取消了索引机制

③HashSet是利用什么机制保证数据去重的

​ 先利用hashCode方法获取元素的哈希值,然后通过equals方法跟原来的数据比较,如果一样的,那么要添加的元素会舍弃,摆正数据的唯一性

(4)练习

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


import java.util.HashSet;
public class _03HashSetTest02 {
    public static void main(String[] args) {
        //1.创建学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);
        Student s4 = new Student("zhangsan",23);

        //2.创建集合添加学生对象
        HashSet<Student> hs = new HashSet<>();

        //3.添加对象到集合中
        System.out.println(hs.add(s1));  //true
        System.out.println(hs.add(s2));  //true
        System.out.println(hs.add(s3));  //true
        System.out.println(hs.add(s4));  //false

        //4.打印集合
        System.out.println(hs); //[Student{name = wangwu, age = 25}, Student{name = lisi, age = 24}, Student{name = zhangsan, age = 23}]
    }
}

4.LinkedHashSet的底层原理

(1)特点

①有序、不重复、五索引
②这里的有序指的是存和取出的元素顺序一致
③原理:

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

import java.util.LinkedHashSet;

public class _04LinkedHashSetTest {
    public static void main(String[] args) {
        //1.创建学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);
        Student s4 = new Student("zhangsan",23);

        //2.创建集合添加学生对象
        LinkedHashSet<Student> hs = new LinkedHashSet<>();

        //3.添加对象到集合中
        System.out.println(hs.add(s2));  //true
        System.out.println(hs.add(s1));  //true
        System.out.println(hs.add(s3));  //true
        System.out.println(hs.add(s4));  //false

        //4.打印集合
        System.out.println(hs);//[Student{name = lisi, age = 24}, Student{name = zhangsan, age = 23}, Student{name = wangwu, age = 25}]
    }
}

(2)注意:

①LinkedHashSet集合的特点和原理

​ ★有序、不重复、无索引

​ ★底层基于哈希表,使用双链表记录添加顺序

②在以后如果要进行数据去重,我们使用哪个

​ ★默认使用HashSet

​ ★如果要求数据去重且存取有序,才使用LinkedHashSet

5.TreeSet

(1)特点:

①不重复、无索引、可排序

②可排序:按照元素的默认规则(从小到大)排序

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

(2)练习

//需求:存储整数并进行排序
import java.util.Iterator;
import java.util.TreeSet;
public class _05TreeSetTest01 {
    public static void main(String[] args) {
        //需求:存储整数并进行排序
        //1.创建TreeSet集合
        TreeSet<Integer> ts = new TreeSet<>();
        //2.添加整数
        ts.add(232);
        ts.add(23);
        ts.add(56);
        ts.add(31);
        ts.add(76);
        ts.add(111);
        //打印集合
        System.out.println(ts);//[23, 31, 56, 76, 111, 232]

        //三种遍历方式
        Iterator<Integer> it = ts.iterator();
        while (it.hasNext()){
            Integer i1 = it.next();
            System.out.println(i1);
        }

        for (Integer i2 : ts) {
            System.out.println(i2);
        }

        ts.forEach(i3 ->System.out.println(i3));
    }
}

(3)TreeSet集合默认的规则

①对于数值类型:

​ Integer、Double默认按照从小到大的顺序进行排列

②对于字符、字符串类型:

​ 按照字符在ASCII码表中的数字升序进行排序

package com_06Gather._01gatherStructure._03SetTest;

import java.util.TreeSet;

public class _06TreeSetTest02 {
    public static void main(String[] args) {
        /*需求:创建TreeSet集合,并添加三个学生对象,学生对象属性,姓名,年龄,
        要求按照学生的年龄进行排序,同年龄按照姓名字母排序(暂不考虑中文),同姓名,同年龄认为是同一个人*/

        //1.创建学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);


        //2.创建集合添加学生对象
        TreeSet<Student> ts = new TreeSet<>();

        //3.添加对象到集合中
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);

        System.out.println(ts);
    }
}

(4)TreeSet的两种比较方式

①方式一(默认):

​ **默认排序/自然排序:**JavaBean类实现Comparable接口指定比较规则

/*需求:创建TreeSet集合,并添加三个学生对象,学生对象属性,姓名,年龄,要求按照学生的年龄进行排序,同年龄按照姓名字母排序(暂不考虑中文),同姓名,同年龄认为是同一个人*/
//创建Student类
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;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }

    @Override
    //this:表示当前要添加的元素
    //o:表示已经在红黑树存在的元素

    //返回值:
    //负数:表示当前要添加的元素是小的,存左边
    //正数:表示当前要添加的元素是大的,存右边
    //0 :表示当前要添加的元素已经存在,舍弃
    public int compareTo(Student o) {
        System.out.println("--------------");
        System.out.println("this:" + this);
        System.out.println("o:" + o);
        //指定排序的规则
        //只看年龄,我想要按照年龄的升序进行排列
        return this.getAge() - o.getAge();
    }
}

//创建测试类
import java.util.TreeSet;
public class _06TreeSetTest02 {
    public static void main(String[] args) {
        /*需求:创建TreeSet集合,并添加三个学生对象,学生对象属性,姓名,年龄,
        要求按照学生的年龄进行排序,同年龄按照姓名字母排序(暂不考虑中文),同姓名,同年龄认为是同一个人*/

        //1.创建学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);


        //2.创建集合添加学生对象
        TreeSet<Student> ts = new TreeSet<>();

        //3.添加对象到集合中
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);

        System.out.println(ts);
    }
}




/*
this:Student{name = zhangsan, age = 23}
o:Student{name = zhangsan, age = 23}
--------------
this:Student{name = lisi, age = 24}
o:Student{name = zhangsan, age = 23}
--------------
this:Student{name = wangwu, age = 25}
o:Student{name = zhangsan, age = 23}
--------------
this:Student{name = wangwu, age = 25}
o:Student{name = lisi, age = 24}
[Student{name = zhangsan, age = 23}, Student{name = lisi, age = 24}, Student{name = wangwu, age = 25}]
*/


②方式二(比较器排序):

​ **比较器排序:**创建TreeSet对象的时候,传递比较器Comparator指定规则

③使用原则

​ 默认使用第一种,如果第一种不能满足当前需求,就使用第二种

/*
            需求:请自行选择比较器排序和自然排序两种方式;
            要求:存入四个字符串, “c”, “ab”, “df”, “qwer”
            按照长度排序,如果一样长则按照首字母排序

            采取第二种排序方式:比较器排序
        */
import java.util.TreeSet;
public class _07TreeSetTest03 {
    public static void main(String[] args) {
       
        //1.创建集合
        TreeSet<String> ts = new TreeSet<>(( o1, o2)-> {
                // 按照长度排序
                int i = o1.length() - o2.length();
                //如果一样长则按照首字母排序
                i = i == 0 ? o1.compareTo(o2) : i;
                return i;
            }
        );
        ts.add("c");
        ts.add("ab");
        ts.add("df");
        ts.add("qwer");

        System.out.println(ts);
    }
}

(5)练习

 /*  需求:创建5个学生对象
        属性:(姓名,年龄,语文成绩,数学成绩,英语成绩),
        按照总分从高到低输出到控制台
        如果总分一样,按照语文成绩排
        如果语文一样,按照数学成绩排
        如果数学成绩一样,按照英语成绩排
        如果英文成绩一样,按照年龄排
        如果年龄一样,按照姓名的字母顺序排
        如果都一样,认为是同一个学生,不存。

        第一种:默认排序/自然排序
        第二种:比较器排序

        默认情况下,用第一种排序方式,如果第一种不能满足当前的需求,采取第二种方式。


        课堂练习:
            要求:在遍历集合的时候,我想看到总分。

      */
//创建标准JavaBean学生类
public class Student2 implements Comparable<Student2>{
    private String name;
    private int age;
    private int yuwen;
    private int math;
    private int english;


    public Student2() {
    }

    public Student2(String name, int age, int yuwen, int math, int english) {
        this.name = name;
        this.age = age;
        this.yuwen = yuwen;
        this.math = math;
        this.english = english;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 获取
     * @return yuwen
     */
    public int getYuwen() {
        return yuwen;
    }

    /**
     * 设置
     * @param yuwen
     */
    public void setYuwen(int yuwen) {
        this.yuwen = yuwen;
    }

    /**
     * 获取
     * @return math
     */
    public int getMath() {
        return math;
    }

    /**
     * 设置
     * @param math
     */
    public void setMath(int math) {
        this.math = math;
    }

    /**
     * 获取
     * @return english
     */
    public int getEnglish() {
        return english;
    }

    /**
     * 设置
     * @param english
     */
    public void setEnglish(int english) {
        this.english = english;
    }

    public String toString() {
        return "Student2{name = " + name + ", age = " + age + ", yuwen = " + yuwen + ", math = " + math + ", english = " + english + "}";
    }

    @Override
    /* 按照总分从高到低输出到控制台
    如果总分一样,按照语文成绩排
    如果语文一样,按照数学成绩排
    如果数学成绩一样,按照英语成绩排
    如果英文成绩一样,按照年龄排
    如果年龄一样,按照姓名的字母顺序排
    如果都一样,认为是同一个学生,不存。*/
    public int compareTo(Student2 o) {
        int sum1 = this.getYuwen()+this.getMath()+this.getEnglish();
        int sum2 = o.getYuwen()+o.getMath()+o.getEnglish();
        //比较两者的总分
        int i = sum1 - sum2;
        //如果总分一样,就按照语文成绩排序
        i = i == 0 ? this.getYuwen() - o.getYuwen() : i;
        //如果语文成绩一样,就按照数学成绩排序
        i = i == 0 ? this.getMath() - o.getMath() : i;
        //如果数学成绩一样,按照英语成绩排序(可以省略不写)
        i = i == 0 ? this.getEnglish() - o.getEnglish() : i;
        //如果英文成绩一样,按照年龄排序
        i = i == 0 ? this.getAge() - o.getAge() : i;
        //如果年龄一样,按照姓名的字母顺序排序
        i = i == 0 ? this.getName().compareTo(o.getName()) : i;
        return i;
    }
    public int getSum(Student2 stu){
        int sum = stu.getYuwen()+stu.getMath()+stu.getEnglish();
        return sum;
    }
}
//创建测试类
import java.util.TreeSet;

public class _08TreeSetTest04 {
    public static void main(String[] args) {

        //1.创建学生对象
        Student2 s1 = new Student2("zhangsan",23,82,87,91);
        Student2 s2 = new Student2("lisi",24,82,77,81);
        Student2 s3 = new Student2("wangwu",25,89,77,89);
        Student2 s4 = new Student2("liumazi",26,86,83,89);
        Student2 s5 = new Student2("qianqi",26,86,80,70);

        //2.创建集合对象
        TreeSet<Student2> ts = new TreeSet<>();

        //3.添加元素
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

        //4,遍历集合
        for (Student2 t : ts) {
            t.getSum(t);
            System.out.print(t);
            System.out.println("总分为"+t.getSum(t));

        }
    }
}
//打印结果
Student2{name = qianqi, age = 26, yuwen = 86, math = 80, english = 70}总分为236
Student2{name = lisi, age = 24, yuwen = 82, math = 77, english = 81}总分为240
Student2{name = wangwu, age = 25, yuwen = 89, math = 77, english = 89}总分为255
Student2{name = liumazi, age = 26, yuwen = 86, math = 83, english = 89}总分为258
Student2{name = zhangsan, age = 23, yuwen = 82, math = 87, english = 91}总分为260

(6)总结

(1)TreeSet集合的特点是怎样的

​ ①不重复、无索引、可排序

​ ②可排序:按照元素的默认规则(从小到大)排序

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

(2)TreeSet集合自定义排序规则有几种方式

①方式一(默认):

​ **默认排序/自然排序:**JavaBean类实现Comparable接口指定比较规则

②方式二(比较器排序):

​ **比较器排序:**创建TreeSet对象的时候,传递比较器Comparator指定规则

(3)方法返回值的特点

​ ①负数:表示当前要添加的元素是小的,放左边

​ ②正数:表示当前要添加的元素是大的,放右边

​ ③0:表示当前要添加的元素已经存在,舍弃

6.使用场景

(1)如果想要集合中的元素可重复

​ ===》用ArrayList集合,基于数组的(用的最多)

(2)如果想要集合中的元素可重复,而且当前的增删操作明显多于查询

​ ===》用LinkedList集合,基于链表的

(3)如果想对集合中的元素去重

​ ===》用HashSet集合,基于哈希表的(用的最多)

(4)如果想对集合中的元素去重,而且保证存取顺利

​ ===》用LinkedHashSet集合,基于哈希表和双链表,效率低于HsahSet

(5)如果想对集合中的元素进行排序

​ ===》用TreeSet集合,基于红黑树,后续也可以用List集合实现排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值