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

本文详细介绍了Java中Set接口的三个主要实现类:HashSet、TreeSet和LinkedHashSet。内容包括它们的特点、常用方法、底层结构分析以及源码解读。HashSet基于HashMap实现,无序且不允许重复;TreeSet内部使用红黑树,能按特定顺序排序;LinkedHashSet保持插入顺序。此外,文章还提供了两个实例来说明如何重写equals和hashCode方法以确保元素唯一性。

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bug 郭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值