java集合专题Set接口及HashSet/LinkedHashSet使用方法底层结构及源码分析

举报
bug郭 发表于 2022/10/06 22:52:10 2022/10/06
【摘要】 大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流作者简介:CSDN java领域新星创作者blog.csdn.net/bug…掘金LV3用户 juejin.cn/user/bug…阿里云社区专家博主,星级博主,developer.a...

大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流

作者简介:

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。