1.Set
那接下来我们来看Collection单列集合体系的第二部分 Set集合。
1.1 Set集合概述和特点
Set集合特点
1.可以去除重复
2.存取顺序不一致
3.没有带索引的方法,所以不能使用普通fori循环遍历,也不能通过索引来获取,删除Set集合里面的元素
Set集合练习
存储字符串并遍历
1.2 数据结构-树
1.3 红黑树
平衡二叉B树
每一个节点可以是红或者黑
红黑树不是高度平衡的,它的平衡是通过“自己的红黑规则"进行实现的
2.HashSet
2.1 HashSet集合概述和特点
HashSet集合特点
1.底层数据结构是哈希表
2.存取无序
3.无索引 所以不能使用普通for循环遍历
4.不能存在重复值
HashSet集合练习
存储字符串并遍历
扩展:什么是哈希表?
什么是哈希表?
哈希表是一种以键值key存储数据value的结构,以key作为标识值存储value值;只要输入待查找的key,即可获取其对应的value值
public HashSet() {
map = new HashMap<>();
}
163/16
2.2 哈希值
在了解哈希表之前我们要先了解哈希值。
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值
public int hashCode():返回对象的哈希码值
在存入set集合的时候会调用hash算法
**哈希算法 **
根据元素的哈希值跟数组的长度求余计算出存入的位置
对象的哈希值特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的
默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
我们在之前重写过Object的equals方法
Java中规定:
(1)如果两个对象通过equals方法比较是相等的,那么它们的hashCode方法结果值也是相等的。
(2)如果两个对象通过equals方法比较是不相等的,那么它们的hashCode方法结果值不一定不相等。
代码展示:
public class Animal {
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Animal)) return false;
Animal animal = (Animal) o;
return Objects.equals(name, animal.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
public class Test {
public static void main(String[] args) {
String s1 = "山东高合";
String s2 = "山东高合";
String s3 = "山东高合科技";
String s4 = "山东高合科技";
System.out.println(s1.hashCode());//725470523
System.out.println(s2.hashCode());//725470523
System.out.println(s3.hashCode());//1393462602
System.out.println(s4.hashCode());//1393462602
Animal a = new Animal();
Animal b = new Animal();
System.out.println(a.hashCode());//31
System.out.println(b.hashCode());//31
System.out.println(a.equals(b));//true
}
}
哈希的常见应用场景:
在日常运营活动中,我们活动开发经常遇到的应用场景是
1.安全加密、
2.唯一标识、
3.数据校验。
4.散列函数
5.负载均衡
常见数据结构之哈希表
哈希表JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组
JDK8以后,在长度比较长的时候,底层实现了优化,采用的是数组+二叉树
HashSet1.7版本原理
164/16
HashSet<String> hm = new HashSet<>();
//是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap
创建一个默认长度16,默认负载因子为0.75的数组,数组名table
根据元素的哈希值跟数组的长度计算出应存入的位置
判断当前位置是否为null,如果是null直接存入
如果位置不为null,表示有元素,则调用equals方法比较属性值
如果一样,则不存,如果不一样,则存入数组,老元素挂在新元素下面
负载因子是在自动增加其哈希表容量之前允许哈希表获得的满度的度量
当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新哈希
(即,内部数据结构将被重建),因此哈希表的存储桶数大约为两倍。
通常,默认负载因子(.75)在时间和空间成本之间提供了一个很好的折中方案。
较高的值会减少空间开销,但会增加查找成本(在HashMap类的大多数操作中都得到体现,包括get和put)。设置映射表的初始容量时,应考虑映射中的预期条目数及其负载因子,以最大程度地减少重新哈希操作的数量。如果初始容量大于最大条目数除以负载因子,则将不会进行任何哈希操作。
HashSet1.8 版本原理
底层结构:哈希表。(数组、链表、红黑树的结合体)。
当挂在下面的元素过多,那么不利于查询,所以在JDK8以后,
当链表长度超过 8 的时候,自动转换为红黑树。
存储流程不变。
转为红黑树之后排序规则:通过红黑的的规则进行对哈希值的比较对红黑树的内容进行排序
2.3 并发工具类
创建一个默认长度16,默认负载因子为0.75的数组,数组名table
根据元素的哈希值跟数组的长度计算出应存入的位置
判断当前位置是否为null,如果是null直接存入
如果位置不为null,表示有元素,则调用equals方法比较属性值
如果一样,则不存,如果不一样,则存入数组,新元素挂在老元素下面
当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
利用HashSet存储自定义元素,必须重写hashCode和equals方法
2.4 案例:HashSet集合存储学生对象并遍历
**需求:**创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
思路:
1.定义学生类
2.创建HashSet集合对象
3.创建学生对象
4.把学生添加到集合
5.遍历集合(增强for)
6.在学生类中重写两个方法hashCode()和equals()自动生成即可
3.TreeSet
3.1 TreeSet集合概述和特点
TreeSet集合特点
1.元素唯一
2.无索引
3.可以将元素按照规则进行排序
TreeSet集合练习
存储Integer类型的整数,并遍历
存储学生对象,并遍历
TreeSet<Student> t1 = new TreeSet<>();
t1.add(new Student("一号",16));
t1.add(new Student("二号",18));
t1.add(new Student("三号",10));
t1.add(new Student("四号",15));
System.out.println(t1);
//Exception in thread "main" java.lang.ClassCastException: com.itgaohe.lesssion14.Student cannot be cast to java.lang.Comparable 报错!!!必须指定排序规则!!
想要使用TreeSet,需要制定排序规则
3.2 自然排序Comparable的使用
1.使用空参构造创建TreeSet集合
2.自定义的Student类实现Comparable接口
3.重写里面的compareTo方法
TreeSet集合练习
改写存储学生对象的案例,并按照年龄从小到大排列
Student
public class Student implements Comparable<Student> {
private String name;
private int age;
...getter/setter
// 方式一:类自定义比较规则
@Override
public int compareTo(Student o) {
return this.age - o.getAge();
}
}
再次运行
3.3 自然排序简单原理图
1.如果返回值为负数,表示当前存入的元素是较小值,存左边
2.如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
3.如果返回值为正数,表示当前存入的元素是较大值,存右边
3.4 案例:按照年龄排序
需求:改写刚刚的学生案例;
要求:按照年龄从小到大排,如果年龄一样,则按照姓名首字母排序
如果姓名和年龄一样,才认为是同一个学生对象,不存入。
代码展示:
Student
// 方式一:类自定义比较规则
@Override
public int compareTo(Student o) {
//按照年龄从小到大排
int r = this.age-o.age;
//年龄一样 咱要首字母存入
r = (r==0?this.name.compareTo(o.getName()):r);
return r;
}
TreeSet<Student> t1 = new TreeSet<>();
t1.add(new Student("amy",15));
t1.add(new Student("betty",15));
t1.add(new Student("carrt",15));
t1.add(new Student("dog",15));
t1.add(new Student("green",15));
t1.add(new Student("hs",16));
t1.add(new Student("icon",17));
t1.add(new Student("efls",15));
t1.add(new Student("fer",15));
t1.add(new Student("fery4",15));
打印结果
[Student{name='amy', age=15}, Student{name='betty', age=15}, Student{name='carrt', age=15}, Student{name='dog', age=15}, Student{name='efls', age=15}, Student{name='fer', age=15}, Student{name='fery4', age=15}, Student{name='green', age=15}, Student{name='hs', age=16}, Student{name='icon', age=17}]
3.5 比较器排序Comparator的使用
1.TreeSet的带参构造方法使用的是比较器排序对元素进行排序的
2.比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
//o1表示现在要存入的那个元素
//o2表示已经存入到集合中的元素
3.重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
练习:
需求:用第二种方式实现下面的需求;
要求:自定义Teacher老师类,属性为姓名和年龄
请按照年龄排序,如果年龄一样,
则按照姓名进行排序。姓名都使用英文字母表示。
代码展示
Teacher
测试:
public static void main(String[] args) {
TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher t1, Teacher t2) {
int age = t1.getAge() - t2.getAge();
age = (age == 0 ? t1.getName().compareTo(t2.getName()) : age);
return age;
}
});
ts.add(new Teacher("z", 18));
ts.add(new Teacher("s", 18));
ts.add(new Teacher("z", 16));
ts.add(new Teacher("a", 18));
System.out.println(ts);
}
3.6 两种比较方式小结
1.自然排序:自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序。
2.比较器排序:创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序。
3.在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,使用比较器排序
两种方式中,关于返回值的规则:
1.如果返回值为负数,表示当前存入的元素是较小值,存左边
2.如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
3.如果返回值为正数,表示当前存入的元素是较大值,存右边
3.7 案例:按照字符串长短排序
需求:请自行选择比较器排序和自然排序两种方式;
要求:存入四个字符串, “c”, “ab”, “df”, “qwer”
按照长度排序,如果一样长则按照首字母排序
public static void main(String[] args) {
Set<String> set = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int r = o1.length() - o2.length();
r = r == 0? o1.compareTo(o2):r;
return r;
}
});
set.add("c");
set.add("ab");
set.add("df");
set.add("qwer");
System.out.println(set);
}
3.8 案例:成绩排序
需求:键盘录入3个学生信息(姓名,语文成绩,数学成绩,英语成绩),
按照总分从高到低输出到控制台
思路:
1.定义学生类
2.创建TreeSet集合对象,通过比较器排序进行排序
3.创建学生对象
4.把学生对象添加到集合
5.遍历集合
代码:
public class Student {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public Student(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"score=" + score +
'}';
}
}
测试:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Student student = null;
Set<Student> set = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getScore()-o2.getScore();
}
});
for (int i = 0; i < 3; i++) {
System.out.println("请输入成绩:");
String score = sc.next();
int age = Integer.parseInt(score);
student = new Student(age);
set.add(student);
}
System.out.println(set);
}