Java集合—Set系列集合(从0到1 详解,附有代码+案例)

Java集合—Set系列集合

18.7 Set系列集合

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

  • 无序:存取顺序不一致
  • 不重复:可以去重复
  • 无索引:没有带索引的方法,不能通过普通for循环遍历

Set集合的实现类

  • HashSet:无序、不重复、无索引
  • LinkedHashSet:有序、不重复、无索引
  • TreeSet:可排序、不重复、无索引
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Test01 {
    public static void main(String[] args) {
        // Set是接口,不能创对象,所以创建实现类的对象
        Set<String> s = new HashSet<>();
        s.add("张三");
        s.add("张三");//添加失败
        s.add("李四");
        s.add("王五");

        // 存取顺序不一样
        System.out.println(s);//[李四, 张三, 王五]

        // 增强for循环
        for (String s1 : s) {
            System.out.print(s1+" ");//李四 张三 王五
        }
        // 迭代器遍历
        Iterator<String> it = s.iterator();
        while (it.hasNext()){
            String str = it.next();
            System.out.print(str+" ");//李四 张三 王五
        }
        // Lambda表达式
        s.forEach((String str)->{
                System.out.print(str);//李四 张三 王五
            }
        );
    }
}

18.8 HashSet

HashSet:无序、不重复、无索引

HashSet底层原理

  • HashSet集合底层采取哈希表存储数据
  • 哈希表是一种对于增删改查数据性能都比较好的结构

哈希表组成

  • JDK8之前:数组+链表
  • JDK8开始:数组+链表+红黑树

哈希值

  • 根据hashCode方法计算出来的int类型的整数

    (对象的整数表现形式)

  • hashCode方法定义在Object类中,所有对象可以调用,默认使用地址值进行计算

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

对象的哈希值特点

  • 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
  • 如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
  • 小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
public class Student {
    private String name;
    public 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;}
  //重写hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
============================================================
public class StudnetTest {
    public static void main(String[] args) {
        Student stu1 = new Student("张三",18);
        Student stu2 = new Student("张三",18);
        System.out.println(stu1.hashCode());//24022538
        System.out.println(stu2.hashCode());//24022538
        // 哈希碰撞
        System.out.println("abc".hashCode());//96354
        System.out.println("acD".hashCode());//96354
    }
}

HashSet在底层原理

在这里插入图片描述

  • 创建一个默认长度16,默认加载因子为0.75的数组,数组名table

    16*0.75 = 12,如果存入的数据达到12,则数组自动扩容为原来的2倍

  • 根据元素的哈希值跟数组的长度计算出应存入的位置

    int index = (数组长度-1) & 哈希值;

  • 如果位置为null,直接存入

  • 如果位置不为null 表示有元素,则调用equals方法比较属性值

    一样;不存 不一样:存入数组,形成链表

    jdk8以前:新元素存入数组,老元素挂在新元素下面

    jdk8以后:新元素直接挂在老元素下面

  • jdk8以后,当链表长度超过8,且数组长度大于等于64时,链表自动转换为红黑树。

  • 如果集合中存储的时自定义对象,必须重写hashCode方法和equals方法。

HashSet是利用什么机制保证去重?

  • 利用HashCode方法和equals方法
  • HashCode方法来计算出哈希值,根据哈希值计算机出要元素要存入的位置,然后再调用equals方法比较对象内部的属性值是否一样

HashSet为什么存和取的顺序不一样?

HashSet为什么没有索引?

Test 泛型是自定义对象

需求:创建一个存储学生对象的集合,存储多个学生对象。
使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认为是同一个对象
import java.util.Objects;

public class Student {
        private String name;
        public 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;}

    @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);
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
===========================================================
import java.util.HashSet;
public class StudentTest {
    public static void main(String[] args) {
        // 创建学生对象
        Student stu1 = new Student("张三",23);
        Student stu2 = new Student("李四",24);
        Student stu3 = new Student("张三",23);
        // 创建集合,添加学生对象到集合中
        HashSet<Student> hs = new HashSet<>();
        System.out.println(hs.add(stu1));//true
        System.out.println(hs.add(stu2));//true
        System.out.println(hs.add(stu3));//false
        System.out.println(hs);
  //[Student{name='张三',age=23},Student{name='李四',age=24}]
    }
}
public class Test {
    public static void main(String[] args) {
        Set set=new HashSet();
        String s1=new String("java");
        String s2=s1;
        String s3=new String("JAVA");
        set.add(s1);
        boolean add = set.add(s2);
        set.add(s3);
        System.out.println(add);//false
        System.out.println(set);//[JAVA, java]
    }
}
public class Note03 {
    public static void main(String[] args) {
        // 创建集合对象
        Set set=new HashSet();

        String s1=new String("java");
        String s2=s1;
        String s3=new String ("java");
        // 添加元素
        set.add(s1);
        boolean add1 = set.add(s2);
        boolean add2 = set.add(s3);

        System.out.println(add1);//false
        System.out.println(add2);//false
        System.out.println(set.size());//1
        System.out.println(set);//[java]

    }
}

18.9 LinkedHashSet

  • 有序、不重复、无索引

    有序指的是存储和取出的元素顺序一致。

原理

在这里插入图片描述

  • 底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序。
public class Student {
        private String name;
        public 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;}

    @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);
    }
    @Override
    public String toString() {
        return "Student{"+"name='"+name+",age="+age+'}';
    }
}
=================================
  public class StudentTest02 {
    public static void main(String[] args) {
        // 创建学生对象
        Student s1 = new Student("张三",23);
        Student s2 = new Student("李四",24);
        Student s3 = new Student("张三",23);
        // 创建集合
        LinkedHashSet<Student> lhs= new LinkedHashSet<>();
        //3.添加元素
        System.out.println(lhs.add(s2));//true
        System.out.println(lhs.add(s1));//true
        System.out.println(lhs.add(s3));//false
        System.out.println(lhs);
      //添加和取出顺序一致
 //[Student{name='李四', age=24},Student{name='张三',age=23}]
    }
}

18.10 TreeSet

  • TreeSet:可排序、不重复、无索引

TreeSet底层是红黑树结构

对于数值类型:Integer 、Double,默认从小到大顺序排序

对于字符、字符串类型:按照字符在ASCll码表中的数字升序进行排序

import java.util.Iterator;
import java.util.TreeSet;
public class Test {
    public static void main(String[] args) {
        /*需求:利用TreeSet存储整数并进行排序*/
        // 创建TreeSet集合对象
        TreeSet<Integer> ts = new TreeSet<>();
        // 添加元素
        ts.add(3);
        ts.add(1);
        ts.add(2);
        // 打印集合
        System.out.println(ts);//[1, 2, 3]
        // 1.迭代器遍历
        Iterator<Integer> it = ts.iterator();
        while (it.hasNext()){
            // jdk5后可以自动装箱,自动拆箱,
            // Integer和int在底层可以互相转换
            // int next = it.next();
            Integer next = it.next();
            System.out.print(next);//1 2 3
        }
        // 2.增强for,此时Integer也可以写成int
        for (Integer t : ts) {System.out.print(t);//123}
        // 3.Lambda表达式
        ts.forEach((Integer integer) ->{
                System.out.print(integer);//123
            }
        );
        // 3.Lambda表达式简写
     ts.forEach(integer ->System.out.print(integer));//123
    }
}

18.10.1 TreeSet的两种比较方式

如果泛型<>里面是自定义类需要自己重新指定排序规则,有两种方式:

方式一:自然排序

默认的排序规则/自然排序:

  • JavaBean类实现Comparable接口指定比较规则

    重写里面的抽象方法,再指定比较规则

案例:

需求:创建TreeSet集合,并添加3个学生对象
学生对象属性: 姓名,年龄。
要求按照学生的年龄进行排序
同年龄按照姓名字母排列(暂不考虑中文)
同姓名,同年龄认为是同一个人

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) {
        //this:表示当前要添加的元素
        //o:表示已经在红黑树存在的元素
        //返回值:
        //负数:表示当前要添加的元素是小的,存左边
        //正数:表示当前要添加的元素是大的,存右边
        //0 :表示当前要添加的元素已经存在,舍弃
        //指定排序的规则
        //只看年龄,按照年龄的升序进行排列
        return this.age-o.age;
    }
}
=========================================================
  public class StudentTest {
    public static void main(String[] args) {
        /*
          方式一:默认的排序规则/自然排序
       Student实现Comparable接口,重写里面的抽象方法,再指定比较规则
         */
        // 创建学生对象
        Student s1 = new Student("zhangsan",23);
        Student s2 = new Student("lisi",24);
        Student s3 = new Student("wangwu",25);
        Student s4 = new Student("zhaoliu",26);
        // 创建集合对象
        TreeSet<Student> ts = new TreeSet<>();
        // 添加元素
        ts.add(s3);
        ts.add(s2);
        ts.add(s1);
        ts.add(s4);
        System.out.println(ts);//23 24 25 26
    }
}
方式二:比较器排序(Comparator)

比较器排序:创建TreeSet对象时,传递比较器Comparator制定规则

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

默认排序:

public class Test {
    public static void main(String[] args) {
            /*
            需求:请自行选择比较器排序和自然排序两种方式;
            要求:存入四个字符串, “c”, “ab”, “df”, “qwer”
            按照长度排序,如果一样长则按照首字母排序
             */
        TreeSet<String> ts = new TreeSet<>();
        ts.add("c");
        ts.add("ab");
        ts.add("df");
        ts.add("qwer");
        //默认方式排序
        System.out.println(ts);//[ab, c, df, qwer]
    }
}

比较器排序

public class Test02 {
    public static void main(String[] args) {
           /*
            需求:请自行选择比较器排序和自然排序两种方式;
            要求:存入四个字符串, “c”, “ab”, “df”, “qwer”
            按照长度排序,如果一样长则按照首字母排序
             */
TreeSet<String> ts= new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                // 按照长度排序,
                int i = o1.length() - o2.length();
                //如果一样长则按照首字母排序,否则按照实际长度为准
                int i1 = i == 0 ? o1.compareTo(o2) : i;
                return i1;
            }
        });
        ts.add("c");
        ts.add("ab");
        ts.add("df");
        ts.add("qwer");
        //默认方式排序
        System.out.println(ts);//[c, ab, df, qwer]
    }
}
  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔚一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值