24-08-07 HashSet与LinkedHashSet的底层机制与源码说明

24-08-07 HashSet与LinkedHashSet的底层机制与源码说明

HashSet

初步认识HashSet

public class HashSet_ {
    public static void main(String[] args) {
        //老韩解读
        //1. 构造器走的源码
        /*
            public HashSet() {
                map = new HashMap<>();
            }
         2. HashSet 可以存放null ,但是只能有一个null,即元素不能重复
         */
        Set hashSet = new HashSet();
        hashSet.add(null);
        hashSet.add(null);
        System.out.println("hashSet=" + hashSet);


    }
}
@SuppressWarnings({"all"})
public class HashSet01 {
    public static void main(String[] args) {
        HashSet set = new HashSet();

        //说明
        //1. 在执行add方法后,会返回一个boolean值
        //2. 如果添加成功,返回 true, 否则返回false
        //3. 可以通过 remove 指定删除哪个对象
        System.out.println(set.add("john"));//T
        System.out.println(set.add("lucy"));//T
        System.out.println(set.add("john"));//F
        System.out.println(set.add("jack"));//T
        System.out.println(set.add("Rose"));//T


        set.remove("john");
        System.out.println("set=" + set);//3个

        //
        set  = new HashSet();
        System.out.println("set=" + set);//0
        //4 Hashset 不能添加相同的元素/数据?
        set.add("lucy");//添加成功
        set.add("lucy");//加入不了
        set.add(new Dog("tom"));//OK
        set.add(new Dog("tom"));//Ok
        System.out.println("set=" + set);

        //在加深一下. 非常经典的面试题.
        //看源码,做分析, 先给小伙伴留一个坑,以后讲完源码,你就了然
        //去看他的源码,即 add 到底发生了什么?=> 底层机制.
        set.add(new String("hsp"));//ok
        set.add(new String("hsp"));//加入不了.
        System.out.println("set=" + set);


    }
}
class Dog { //定义了Dog类
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}

数组链表模拟

hashset的底层实际上是hashmap,hashmap的底层是数组加链表加红黑树。

public class HashSetStructure {
    public static void main(String[] args) {
        //模拟一个HashSet的底层 (HashMap 的底层结构)
        //1. 创建一个数组,数组的类型是 Node[]
        //2. 有些人,直接把 Node[] 数组称为 表
        Node[] table = new Node[16];

        //3. 创建结点
        Node john = new Node("john", null);

        table[2] = john;
        Node jack = new Node("jack", null);
        john.next = jack;// 将jack 结点挂载到john
        Node rose = new Node("Rose", null);
        jack.next = rose;// 将rose 结点挂载到jack

        Node lucy = new Node("lucy", null);
        table[3] = lucy; // 把lucy 放到 table表的索引为3的位置.
        System.out.println("table=" + table);


    }
}
class Node { //结点, 存储数据, 可以指向下一个结点,从而形成链表
    Object item; //存放数据
    Node next; // 指向下一个结点

    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }
}

image-20240807233726907

HashSet的扩容机制

第一次扩容大小为16,加载因子为0.75,临界值为12.此后每扩容一次,大小乘2倍。

HashSet的源码解析

@SuppressWarnings({"all"})
public class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("php");
        hashSet.add("java");
        //哈希码是一种可以唯一标识一个对象的整数值,通常用于哈希表等数据结构中。
    }
}
1. 执行 HashSet()
    public HashSet() {
        map = new HashMap<>();
    }

image-20240807235934462

2. 执行 add()
   public boolean add(E e) {//e = "java"
        return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
   }

image-20240808000006236

3.执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16)
    public V put(K key, V value) {//key = "java" value = PRESENT 共享
       return putVal(hash(key), key, value, false, true);
   }

image-20240808000030189

image-20240808000053680

4.执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
          boolean evict) {
       Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
       //table 就是 HashMap 的一个数组,类型是 Node[]
       //if 语句表示如果当前table 是null, 或者 大小=0
       //就是第一次扩容,到16个空间.
       if ((tab = table) == null || (n = tab.length) == 0)
           n = (tab = resize()).length;

       //(1)根据key,得到hash 去计算该key应该存放到table表的哪个索引位置
       //并把这个位置的对象,赋给 p
       //(2)判断p 是否为null
       //(2.1) 如果p 为null, 表示还没有存放元素, 就创建一个Node (key="java",value=PRESENT)
       //(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)

       if ((p = tab[i = (n - 1) & hash]) == null)
           tab[i] = newNode(hash, key, value, null);
       else {
           //一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
           Node<K,V> e; K k; //
           //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
           //并且满足 下面两个条件之一:
           //(1) 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象
           //(2)  p 指向的Node 结点的 key 的equals() 和准备加入的key比较后相同
           //就不能加入
           if (p.hash == hash &&
               ((k = p.key) == key || (key != null && key.equals(k))))
               e = p;
           //再判断 p 是不是一颗红黑树,
           //如果是一颗红黑树,就调用 putTreeVal , 来进行添加
           else if (p instanceof TreeNode)
               e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
           else {//如果table对应索引位置,已经是一个链表, 就使用for循环比较
                 //(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
                 //    注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
                 //    , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
                 //    注意,在转成红黑树时,要进行判断, 判断条件
                 //    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                 //            resize();
                 //    如果上面条件成立,先table扩容.
                 //    只有上面条件不成立时,才进行转成红黑树
                 //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break

image-20240808000216420

image-20240808000226362

public class HashSetIncrement {
    public static void main(String[] args) {
        /*
        HashSet底层是HashMap, 第一次添加时,table 数组扩容到 16,
        临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12
        如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,
        新的临界值就是 32*0.75 = 24, 依次类推

         */
        HashSet hashSet = new HashSet();
//        for(int i = 1; i <= 100; i++) {
//            hashSet.add(i);//1,2,3,4,5...100
//        }
        /*
        在Java8中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),
        并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),
        否则仍然采用数组扩容机制

         */

//        for(int i = 1; i <= 12; i++) {
//            hashSet.add(new A(i));//
//        }


        /*
            当我们向hashset增加一个元素,-> Node -> 加入table , 就算是增加了一个size++

         */

        for(int i = 1; i <= 7; i++) {//在table的某一条链表上添加了 7个A对象
            hashSet.add(new A(i));//
        }

        for(int i = 1; i <= 7; i++) {//在table的另外一条链表上添加了 7个B对象
            hashSet.add(new B(i));//
        }



    }
}

class B {
    private int n;

    public B(int n) {
        this.n = n;
    }
    @Override
    public int hashCode() {
        return 200;
    }
}

class A {
    private int n;

    public A(int n) {
        this.n = n;
    }
    @Override
    public int hashCode() {
        return 100;
    }
}

HashSet练习题

public class HashSetExercise {
    public static void main(String[] args) {
        Employee xiaoming = new Employee("xiaoming", 10000,new MyDate(2005,9,25));
        Employee xiaohong = new Employee("xiaohong", 5000,new MyDate(2004,8,23));
        Employee xiaofang = new Employee("xiaoming", 1000,new MyDate(2005,9,25));
        HashSet hashSet = new HashSet();
        hashSet.add(xiaofang);
        hashSet.add(xiaoming);
        hashSet.add(xiaohong);
        System.out.println(hashSet);
    }
}
class Employee {
    private String name;
    private double sal;
    private MyDate birthday;

    public Employee(String name, double sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) return true;
        if (object == null || getClass() != object.getClass()) return false;
        Employee employee = (Employee) object;
        return Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, birthday);
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", birthday=" + birthday +
                '}';
    }
}
class MyDate {
    private int year, month, day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) return true;
        if (object == null || getClass() != object.getClass()) return false;
        MyDate myDate = (MyDate) object;
        return year == myDate.year && month == myDate.month && day == myDate.day;
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }
}

LinkedHashSet

LinkedHashSet的初步介绍

image-20240808002417571

image-20240808002437790

LinkedHashSet的源码解析

@SuppressWarnings({"all"})
public class LinkedHashSetSource {
    public static void main(String[] args) {
        //分析一下LinkedHashSet的底层机制
        Set set = new LinkedHashSet();
        set.add(new String("AA"));
        set.add(456);
        set.add(456);
        set.add(new Customer("刘", 1001));
        set.add(123);
        set.add("HSP");

        System.out.println("set=" + set);
        //老韩解读
        //1. LinkedHashSet 加入顺序和取出元素/数据的顺序一致
        //2. LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)
        //3. LinkedHashSet 底层结构 (数组table+双向链表)
        //4. 添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry
        //5. 数组是 HashMap$Node[] 存放的元素/数据是 LinkedHashMap$Entry类型
        /*
                //继承关系是在内部类完成.
                static class Entry<K,V> extends HashMap.Node<K,V> {
                    Entry<K,V> before, after;
                    Entry(int hash, K key, V value, Node<K,V> next) {
                        super(hash, key, value, next);
                    }
                }

         */

    }
}
class Customer {
    private String name;
    private int no;

    public Customer(String name, int no) {
        this.name = name;
        this.no = no;
    }
}

image-20240808002957114

LinkHashSet练习题

public class LinkedHashSetExercise {
    public static void main(String[] args) {

        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(new Car("奥拓", 1000));//OK
        linkedHashSet.add(new Car("奥迪", 300000));//OK
        linkedHashSet.add(new Car("法拉利", 10000000));//OK
        linkedHashSet.add(new Car("奥迪", 300000));//加入不了
        linkedHashSet.add(new Car("保时捷", 70000000));//OK
        linkedHashSet.add(new Car("奥迪", 300000));//加入不了

        System.out.println("linkedHashSet=" + linkedHashSet);

    }
}

/**
 * Car 类(属性:name,price),  如果 name 和 price 一样,
 * 则认为是相同元素,就不能添加。 5min
 */

class Car {
    private String name;
    private double price;

    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "\nCar{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }

    //重写equals 方法 和 hashCode
    //当 name 和 price 相同时, 就返回相同的 hashCode 值, equals返回t

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        return Double.compare(car.price, price) == 0 &&
                Objects.equals(name, car.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, price);
    }
}

image-20240808002557476

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值