java集合专题Set接口及HashSet/LinkedHashSet使用方法底层结构及源码分析
大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流
作者简介:
- CSDN java领域新星创作者blog.csdn.net/bug…
- 掘金LV3用户 juejin.cn/user/bug…
- 阿里云社区专家博主,星级博主,developer.aliyun.com/bug…
- 华为云云享专家 bbs.huaweicloud.com/bug…
Set接口及常用方法
我们主要学习Set接口下的
HashSet/TreeSet/LinkedHashSet
这3个Set的实现类!
并且通过源码的方式学习其底层结构分析源码和他们的扩容方式!
Set特点:
- 无序,保存顺序和取出顺序不一致
- 不允许重复值, 可以保存null值!
Set接口下的常用方法
- Set接口实现了Collection接口遍历方式有2种!不能通过
for
循环进行遍历,因Set没有提供get方法!
public class Set_ {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i <10; i++) {
set.add(i);
}
//遍历方式
System.out.println("==迭代器遍历==");
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.print(next+" ");
}
System.out.println("\n==增强for==");
for (Integer x :set) {
System.out.print(x+" ");
}
}
}
HashSet
HashSet实现了Set接口!
HashSet特点:
常用方法
可以看到HashSet的方法和Set接口的方法差不多,就不演示了!
底层结构
HashSet
实际上是HashMap
其底层是维护了一个map
这个map是数组+链表+红黑树结构
HashSet的构造器会去调用HashMap的构造器!
HashSet特点:
- 无序,插入顺序和取出顺序不一致(因为插入元素时要先进行hash然后保存到对应索引!)
- 不能重复,可以为null
源码分析
分析HashSet的源码其实就是分析HashMap的源码!
HashMap底层: 数组+链表+红黑树
HashSet添加元素底层实现机制
这里的底层实现机制是通过JDK1.8进行分析得到的!
- HashSet底层是HashMap
- 添加一个元素先通过hash(调用其hashCode方法再通过转换成hash值)得到hash值,再将hash值转化成数组的索引值
- 找到该索引位置,如果该位置已经有值了,就进行比较其值是否相同(这里通过比较对象(引用)是否相同或者equals比较相同) 如果相同就不进行添加错操作,如果不相同就添加在这条链表的尾插
- 扩容机制,jdk8 ,如果当前size(元素个数大于阀值(装载因子(0.75) * size)就进行)2倍数组扩容!
如果一条链表中的长度大于8并且table表数组的长度大于等于64就进行树化转化成红黑树
如果已经有链表长度大于8,并且还在进行尾插,并且数组长度并没有到达64每次进行该条链表的添加就会进行数组2倍扩容直到64进行树化!
我们下面通过IDEA然后debug进行验证
public class HashSet_ {
public static void main(String[] args) {
HashSet<Integer> hashSet = new HashSet();
for (int i = 0; i < 10; i++) {
hashSet.add(i);
}
}
}
-
初始化hashSet
-
添加第一个元素
添加一个元素后
容量变为 16
阀值 12
size 元素个数 1 -
当元素个数超过阀值(12) 进行二次扩容 16(12)-> 32(24)
HashSet<Integer> hashSet = new HashSet();
for (int i = 0; i < 13; i++) {
hashSet.add(i);
}
- 进行红黑树树化
单条链表节点长度大于8就要进行树化判断,如果容量未到达64就进行一次2倍扩容,否则就对这条链进行红黑树化操作
总结:
- 超过阀值数组扩容
- 单个链表长度大于8并且数组长度大于等于64链表树化
- 两个值判断相等 1.先hash 判断是否相同2.hash相同判断引用或者equals相同就是相同值,不添加!
HashSet 练习
问题1:定义一个Employee类,该类包含: name和 age
(1).创建3个Emyloyee对象加入到HashSet
(2).当name和age相同认为员工相同,不添加’
public class HashSet_Exercise1 {
//只要重写了Employee的 hashcode 和equals方法即可
// 判断name相同和age相同就是相同值
static class Employee{
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "\nEmployee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
HashSet<Employee> hashSet = new HashSet<>();
hashSet.add(new Employee("刘备",18));
hashSet.add(new Employee("李白",15));
hashSet.add(new Employee("刘备",18));
System.out.println("hashSet:"+hashSet);
}
}
问题2:定义Employee类属性有name,sal,birthday(MyDate类)
其中MyDate包括year,month,day
(1).创建3个对象添加
(2).当name和birthday相同认为员工相同,不添加
public class HashSet_Exercise2 {
//只要重写了Employee的 hashcode 和equals方法即可
// 判断name相同和age相同就是相同值
static class Employee{
private String name;
private int sal;
private MyDate myDate;
@Override
public String toString() {
return "\nEmployee{" +
"name='" + name + '\'' +
", sal=" + sal +
", myDate=" + myDate +
'}';
}
@Override //equals重写时不比较sal即可
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) && Objects.equals(myDate, employee.myDate);
}
@Override//hashCode时不传sal即可!
public int hashCode() {
return Objects.hash(name,myDate);
}
public Employee(String name, int sal, MyDate myDate) {
this.name = name;
this.sal = sal;
this.myDate = myDate;
}
static class MyDate{
private String year;
private int month;
private int day;
public MyDate(String year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyDate myDate = (MyDate) o;
return month == myDate.month &&
day == myDate.day &&
Objects.equals(year, myDate.year);
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
@Override
public String toString() {
return "MyDate{" +
"year='" + year + '\'' +
", month=" + month +
", day=" + day +
'}';
}
}
}
public static void main(String[] args) {
HashSet<Employee> hashSet = new HashSet<>();
hashSet.add(new Employee("刘备",18,new Employee.MyDate("1",12,1)));
hashSet.add(new Employee("李白",15,new Employee.MyDate("1",12,1)));
hashSet.add(new Employee("刘备",18,new Employee.MyDate("1",12,1)));
System.out.println("hashSet:"+hashSet);
}
}
可以知道我们如果重写了equals方法和hashCode方法,就会比较其对象下的值,通过判断其值是否相同判断两个key是否相同!
LinkedHashSet
LinkedHashSet继承了HashSet并且实现了Set接口!
而Linked底层又是LinkedHashMap 数组+双向链表实现
维护了一个hash表和双向链表!
特点:
- 存入顺序和取出顺序一致
- 可以为null
常用方法
可以看到LinkedHashSet自己的方法只有一个,我们通常通过向上转型使用父类HashSet的方法!
所以这里就不演示了!
底层结构
LinkedHashSet底层是由
HashMap
然后底层是数组+双向链表
源码分析
- 初始化
这里和HashSet区别就是这里多了前指针!
双向链表,然后其他机制和HashMap一样!
TreeSet
- 点赞
- 收藏
- 关注作者
评论(0)