0
点赞
收藏
分享

微信扫一扫

大数据Java基础DAY20——集合(Set接口,HashSet类,LinkedHashSet类,TreeSet类)

GG_lyf 2022-02-18 阅读 190

目录

Set接口

Set接口概述

HashSet类

HashSet类概述

HashSet如何保证元素唯一性

Set案例

LinkedHashSet类

LinkedHashSet类概述

LinkedHashSet集合案例

TreeSet类

TreeSet类概述

TreeSet是如何保证元素的排序和唯一性的

TreeSet案例


Set接口

Set接口概述

一个不包含重复元素的 collection。  ( Collection接口  )

HashSet类

HashSet 类实现了Set接口,线程不安全,效率高

HashSet类概述

a.不保证 set 的迭代顺序

b.特别是它不保证该顺序恒久不变。

HashSet如何保证元素唯一性

a.底层数据结构是哈希表(元素是链表的数组)

b.哈希表依赖于哈希值存储

c.添加功能底层依赖两个方法:

int hashCode()

boolean equals(Object obj)

Set案例

(由于Set是一个接口,需具体类实例化,这里用hashSet类举例)

案例1:存储字符串并遍历

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

public class SetTest1 {
public static void main(String[] args) {
//创建Set集合对象
Set<String> set =new HashSet<>();

//向set集合添加元素
set.add("java");
set.add("hive");
set.add("spark");
set.add("flink");
set.add("flink");
set.add("bigdata");
set.add("hadoop");

//增强for循环遍历
for (String s:set){
System.out.println(s);
}
}
}

         可以看出set集合是无序且不重复的

案例2:存储自定义对象并遍历

首先封装一个学生类    (  封装  )

public class Student {
private String name;
private int age;

//无参,有参构造方法
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}

//get方法
public String getName() {
return name;
}
public int getAge() {
return age;
}

//set方法
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}

//toString方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

再创建集合存储学生对象遍历

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

public class SetTest2 {
public static void main(String[] args) {
//创建Set集合对象
Set<Student> set = new HashSet<>();

//创建学生对象
Student s1 =new Student("student1",20);
Student s2 =new Student("student2",17);
Student s3 =new Student("student3",22);
Student s4 =new Student("student4",20);
Student s5 =new Student("student1",20);

//向集合添加元素
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);

//增强for循环遍历
for (Student s :set){
System.out.println(s.getName()+"-----"+s.getAge());
}
}
}

 遍历后发现有重复的对象,这是因为HashSet中add()方法实际上与hashCode()方法和equals()方法有关(可以去看Set集合中add()方法的源码),所以集合会不会去重取决与元素类中有没有重写hashCode()方法与equals()方法,而这里的元素类是Student类,Student类中没有重写这两个方法,所以调用Object父类中的equals()方法,而Object类中的equals()方法比较的是地址值,学生对象都是new出来的,所以地址值肯定不一样,所以集合认为这两个对象不同,就没有去重。

解决方法:在Student类中重写hashCode()与equals()方法

 //直接快捷键 Alt+Insert 选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);
}

LinkedHashSet类

HashSet 类实现Set接口,而LinkedHashSet类继承自HashSet 类也实现了Set接口。

线程不安全,效率高

LinkedHashSet类概述

a.元素有序唯一

b.由链表保证元素有序(存储和取出顺序一致)

c.由哈希表保证元素唯一

LinkedHashSet集合案例

例:存储并遍历字符串

import java.util.LinkedHashSet;

public class LinkedHashSetTest1 {
public static void main(String[] args) {
//创建集合对象
LinkedHashSet<String> set = new LinkedHashSet<>();

//向集合添加元素
set.add("java");
set.add("hive");
set.add("java");
set.add("spark");
set.add("bigdata");

//增强for循环遍历
for (String s:set){
System.out.println(s);
}
}
}

TreeSet类

线程不安全的,效率高

TreeSet类概述

元素唯一,两种排序方式:

a.使用元素的自然顺序对元素进行排序  (自然排序:无参构造方法)

b.或者根据创建 set 时提供的 Comparator 进行排序   (比较器排序:有参构造方法)

  具体取决于使用的构造方法。

TreeSet是如何保证元素的排序和唯一性的

底层数据结构是红黑树(红黑树是一种自平衡的二叉树)

TreeSet案例

例1:TreeSet集合存储数字

import java.util.TreeSet;

public class TreeSetTest1 {
public static void main(String[] args) {
//创建TreeSet集合对象
TreeSet<Integer> set = new TreeSet<>(); //集合只能存放引用数据类型,不能存放基础数据类型,Integer是int类的封装类

//向集合添加元素
set.add(33);
set.add(2);
set.add(45);
set.add(53);
set.add(2);
set.add(78);

//增强for循环遍历
for (Integer i :set){
System.out.println(i);
}
}
}

 可以看出结果中元素是唯一且有序的(这里无参构造走的是自然排序的方法,底层源码中涉及到了Comparable接口,有一个向下转型,而Integer类中实现了Comparable接口,这个接口中有ConparaTo方法,所以遍历有序)

部分源码图片:(根据返回值存放元素位置)

 

例2:TreeSet存储自定义对象(学生类)

首先封装一个公共的学生类(同上)

再创建TreeSet集合存储对象测试

import java.util.TreeSet;

public class TreeSetTest2 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> set = new TreeSet<>();

//创建学生对象
Student s1 =new Student("we2323",23);
Student s2 =new Student("df532",26);
Student s3 =new Student("fg3435",16);
Student s4 =new Student("df532",26);
Student s5 =new Student("dsf24124",20);

//向集合添加元素
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);

//遍历
for (Student s:set){
System.out.println(s);
}
}
}

 然后就报错了,因为这里也是无参构造创建的集合,走的也是自然排序,而自定义的Student类中并没有实现Comparable接口,更没有向下转型,所以报类型转换异常的错误。

解决方法:修改Student类实现Comparade接口

public class Student implements Comparable<Student> {}

再重写ComparaTo方法,修改返回值,按自定义方法排序,如按年龄排序(根据源码方法实现)

  @Override
public int compareTo(Student o) {
//比较年龄大小
int i1=this.age-o.age;
//年龄一样姓名不一定一样
int i2= i1==0?this.name.compareTo(o.name):i1; //三目运算判断当年龄一样时,姓名是否一样,一样直接返回i1,不一样就继续按姓名排序

return i2; //返回值决定元素位置,小于0的放左边,大于0放右边,等于0就重复了去重
}

 

例3:利用TreeSet创建对象时带参数的构造方法进行比较器排序

有参构造源码部分图片:

 

(我们继续利用学生类,这次按名字长度为主来排序)

首先封装一个学生类(不用按无参构造中实现Comparable接口了,初始的即可)

public class Student  {
private String name;
private int age;

//无参,有参构造方法
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}

//get方法
public String getName() {
return name;
}
public int getAge() {
return age;
}

//set方法
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}

//toString方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

然后创建一个类去实现Comparator接口,重写compara方法(有参构造了,注意不是Comparable接口了

import java.util.Comparator;

public class ComparableTest implements Comparator<Student> {
//重写compara方法
@Override
public int compare(Student o1, Student o2) { //这里面自定义你的排序方法,我这里按名字长度为主排序
//先比较名字长度
int i1=o1.getName().length()-o2.getName().length();

//若姓名长度一样,比较姓名内容
int i2= i1==0?o1.getName().compareTo(o2.getName()):i1; //姓名长度不一样直i1赋给i2

//若姓名内容也一样,比较年龄
int i3= i2==0?o1.getAge()-o2.getAge():i2;
return i3;//返回值决定元素位置,小于0的放左边,大于0放右边,等于0就重复了去重
}
}

最后用有参构造创建集合,把自定义的类放进去测试

import java.util.TreeSet;

public class TreeSetTest3 {
public static void main(String[] args) {
//创建自定义类的对象
ComparableTest co = new ComparableTest();
//创建集合
TreeSet<Student> set = new TreeSet<>(co); //把自定义类的对象放进去

//创建学生对象并添加
Student s1 =new Student("qq1224",24);
Student s2 =new Student("qq1224",26);
Student s3 =new Student("rw333",24);
Student s4 =new Student("ree434343",26);
Student s5 =new Student("ere2232",16);
Student s6 =new Student("qq1224",24);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
set.add(s6);

//遍历
for (Student s :set){
System.out.println(s);
}
}
}

 可以看到结果已经按名字长度为主排序了,也去重了,这就是比较器排序。

如果以后要你需要写多个比较器排序,这样写工作量就太大了,可以用匿名内部类改进

 TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {  //这样创建对象就行了
@Override
public int compare(Student o1, Student o2) { //里面的方法一样

int i1=o1.getName().length()-o2.getName().length();

int i2= i1==0?o1.getName().compareTo(o2.getName()):i1;

int i3 = i2==0?o1.getAge()-o2.getAge():i2;
return i3;
}
});

 比较方法跟原来一样,就是直接在构造时new了一个类实现Comparator接口。

举报

相关推荐

0 条评论