文章目录
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]
}
}