一、Set系列:
1.Set系列集合:
无序:存取顺序不一样
不重复:可以去除重复
无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引获取元素
2.Set集合的实现类:
HashSet:无序,不重复,无索引
LinkedHashSet:有序,不重复,无索引
TreeSet:可排序,不重复,无索引
3.Set接口中的方法基本上与Collection的API一致
练习:存储字符串并遍历
利用Set系列的集合,添加字符串,并使用多种方式遍历
①迭代器②增强for③Lambda表达式
package a06mysetdemo;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Consumer;
public class SetDemo1 {
public static void main(String[] args) {
//1.创建一个Set集合的对象
Set<String> s=new HashSet<>();
//2.添加元素
//如果当前元素是第一次添加,那么可以添加成功,返回true
//如果当前元素是第二次添加,那么添加失败,返回false
boolean r1 = s.add("zhangsan");
boolean r2 = s.add("zhangsan");
s.add("lisi");
s.add("wangwu");
// System.out.println(r1);//true
// System.out.println(r2);//false
// System.out.println(s);//[zhangsan]
//3.打印集合
//无序的,存和取的顺序有可能不一样
//System.out.println(s);//[lisi, zhangsan, wangwu]
//迭代器遍历
// Iterator<String> it = s.iterator();
// while(it.hasNext()){
// String str = it.next();
// System.out.println(str);
// }
//增强for
// for (String str : s) {
// System.out.println(str);
// }
//lambda表达式
s.forEach( str-> System.out.println(str));
}
}
运行结果:
二、HashSet
1.底层原理:
HashSet集合底层采取哈希表存储数据
哈希表是一种对于增删改查数据性能都较好的结构
2.哈希表组成:
JDK8以前:数组+链表
JDK8以后:数组+链表+红黑树
3.哈希值:对象的整数表现形式
①根据hashCode方法算出来的int类型的整数
②该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
③一般情况下会重写hashCode方法,利用对象内部的属性值计算哈希值
4.对象的哈希值特点:
如果没有重写hashCode方法,不同对象的哈希值是不同的
如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
代码演示:Student类:
package a06mysetdemo;
import java.util.Objects;
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;
}
//重写hashCode
@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);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试类:
package a06mysetdemo;
public class HashSetDemo1 {
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());
System.out.println(s2.hashCode());
}
}
没重写hashCode方法之前运行结果:
重写hashCode方法之后运行结果:
5.HashSet JDK8 以前,JDK8以后底层原理
①创建一个默认长度16,默认加载因子为0.75的数组,数组名table
加载因子:扩容时机:当数组里面存了16*0.75=12个元素的时候,此时数组就会扩容成原来的两倍16*2=32
②根据元素的哈希值跟数组的长度计算出应存入的位置
哈希值计算公式:int index=(数组长度-1) & 哈希值;
③判断当前位置是否为null,如果是null直接存入
④如果位置不为null,表示有元素,则调用equals方法比较属性值
⑤一样:不存 不一样:存入数组,形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面
JDK8以后:当链表长度大于8而且数组长度大于等于64时,当前链表会自动转化成红黑树
6.HashSet的三个问题:
问题1:HashSet为什么存和取顺序不一样
HashSet是从0索引一条链表,一条链表遍历的
问题2:HashSet为什么没有索引
因为HashSet 底层有数组链表红黑树,一条链表上有多个元素规定谁是0索引不好规定,所以没有
问题3:HashSet是利用什么机制保证数据去重的
利用hashCode方法和equals方法,利用hashCode方法得到哈希值,得到哈希值就能确定当前元素是在数组的哪个位置,再调用equals方法去比较对象内部属性值是否相同
所以如果HashSet里面存储的是自定义对象,那么一定要重写HashCode和equals方法
7.练习:利用HashSet集合去除重复元素
需求:创建一个存储学生对象的集合,存储多个学生对象,实用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
Student类:
package a06mysetdemo;
import java.util.Objects;
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;
}
//重写hashCode
@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);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试类:
package a06mysetdemo;
import java.util.HashSet;
public class HashSetDemo2 {
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));
System.out.println(hs.add(s2));
System.out.println(hs.add(s3));
System.out.println(hs.add(s4));
//4.打印集合
System.out.println(hs);
}
}
运行结果:
三、LinkedHashSet
1.底层原理:有序,不重复,无索引
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序
(图片来自哔站黑马程序员)
代码演示:
Student类:
package a06mysetdemo;
import java.util.Objects;
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;
}
//重写hashCode
@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);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试类:
package a06mysetdemo;
import java.util.LinkedHashSet;
public class LinkedHashSetDemo1 {
public static void main(String[] args) {
//1.创建4个学生对象
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> lhs=new LinkedHashSet<>();
//3.添加元素
System.out.println(lhs.add(s1));
System.out.println(lhs.add(s2));
System.out.println(lhs.add(s3));
System.out.println(lhs.add(s4));
//4.打印集合
System.out.println(lhs);
}
}
运行结果:
注意:如果数据去重,默认使用HashSet;如果要求去重且存取有序, 才使用LinkedHashset