JAVA------集合之Set

一、Set接口

1、无序,没有索引

2、不允许重复元素,最多一个null

3、Set遍历可以使用迭代器和增强for,不能使用下标遍历

package com.level7.Set_;

import sun.font.EAttribute;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Set01 {
    public static void main(String[] args) {
        Set set = new HashSet();
        set.add(2);
        set.add(2);
        set.add(3);
        set.add("jack");
        set.add(5);
        set.add("xrj");
        set.add("xrj");
        set.add("xyy");
        System.out.println(set);
        set.remove("xrj");
        System.out.println(set);


        // 迭代器遍历
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
        }

        // 增强for遍历
        for (Object o : set) {
            System.out.println(o);
        }
        
        // set不能使用下标遍历
    }
}


二、HashSet

1、HashSet实际上是HashMap

在这里插入图片描述

2、HashSet不保证元素是有序的,hash后再确定索引的值

3、不能有重复元素和对象

package com.level7.Set_;

import java.util.HashSet;

public class HashSetSource {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        // 添加成功返回true,添加失败,返回false
        System.out.println(set.add("xrj"));
        System.out.println(set.add("xrj"));
        System.out.println(set.add("xyy"));
        System.out.println(set.add("xyy"));
        System.out.println(set.add("abc"));

        set.remove("xrj");
        System.out.println(set);

        System.out.println("=========================");
        set = new HashSet();
        set.add("xrj"); // 添加成功
        set.add("xrj"); // 添加失败
        set.add(new Dog("xyy"));    // 添加成功
        set.add(new Dog("xyy"));    // 添加成功
        System.out.println(set);
        
        set.add(new String("abc")); // 添加成功
        set.add(new String("abc")); // 添加失败
        System.out.println(set);
    }
}

class Dog {
    private String name;

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

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



HashSet底层机制

HashSet底层机制是HashMap,HashMap底层是数组+链表+红黑树

在这里插入图片描述

  • 如果一条链表元素个数达到8,但是table大小没有达到64,会先对table进行扩容
package com.level7.Set_;

import java.util.HashSet;

public class HashSource02 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("Java");
        hashSet.add("C++");
        hashSet.add("Java");
        System.out.println(hashSet);
    }
}

1、第一次add

1、HashSet hashSet = new HashSet();执行该语句,其实是创建了一个HashMap

2、hashSet.add("Java");第一次执行add方法。实际调用的是map.put方法,里边的PRESENT是一个final类型的静态对象,默认为空。

在这里插入图片描述

3、put方法的key就是准备add的变量Java,value就是默认的PRESENT。调用putVal方法,传入的是key的hash值(传这个是为了在下次传入相同元素时做判断),key、value

在这里插入图片描述

​ 3.1、首先计算key的hash值,如果key为空,他的hash值就是0,否则,他的hash值就是key的hashCode和它本身无符号右移16位做异或运算

4、进入putVal方法。Node[] tab; Node p; int n, i;这是定义辅助变量

table就是HashMap的一个数组,初始为null

在这里插入图片描述

​ 4.1 将table赋值给tab,如果tab为空或者他的长度为0,那么就进resize()方法

​ 4.3 resize()后,现在tab就是大小为16的数组,n的大小为16,然后if ((p = tab[i = (n - 1) & hash]) == null)表示,让 i 指向当前元素应该存在在 数组的那个下标位置,p是当前下标位置的对象,如果为null,表示该位置没存放东西,就存放在这个位置。

​ 4.4 afterNodeInsertion(evict)是一个空实现,留给HashMap的子类实现,最后返回null表示添加成功,然后第三点put方法返回null,到第二点的add方 法,如果返回null,表示add成功

在这里插入图片描述
在这里插入图片描述

​ 4.2、进入resize()方法,让oldTab指向初始的table(初始为空),如果oldTab为空oldCap = 0 否则 oldCap = oldTab.length

newCap表示新的table长度,newThr相当于新的阈值(当table容量达到阈值大小就扩容),第一次时,oldCap == 0,进入else语句,

newCap = DEFAULT_INITIAL_CAPACITY,初始为16,表示第一次扩容大小为16,

newThr = (int)(DEFAULT_LOAD_FACTOR) * DEFAULT_INITIAL_CAPACITY DEFAULT_LOAD_FACTOR默认为0.75,因此现在阈值为12,即超过12的大 小(即插入第13个元素时),数组就要继续扩容。然后创建大小为newCap的数组newTab,并赋给table,最后返回newTab

在这里插入图片描述



2、第二次add不同的值

1、hashSet.add("C++");执行这条语句,首先执行add方法
在这里插入图片描述

2、计算hash然后执行put方法

3、执行putVal方法,此时table不为空,找到赋值的位置,加入进去,然后返回null

在这里插入图片描述


3、add相同的值

1、hashSet.add("Java");执行这条语句。首先执行add方法

在这里插入图片描述

2、计算完hash值后,执行put方法

在这里插入图片描述

3、执行putVal方法,此时table不为空,且当前元素为重复值,因此应该赋值的位置不为空,所以进入elsepp = tab[i = (n - 1) & hash]通过这条语句找到的当前值应该插入的数组对应下标的对象,如果当前值的hashp.hash相等,并且(k = p.key) == key表示是同一个对象或者(key != null && key.equals(k)))表示通过equals方法(该方法通过程序员自己指定)比较两个对象内容相同,那么就是和当前位置对象是相同的,就不能插入。如果不满足,进入else if,判断当前链表是否是红黑树,如果是红黑树,那么用红黑树方法查找。如果不是红黑树,进入else,此时表示就在当前这条链表上查找,e = p.next从第二个元素开始查找,如果下一个元素为空,那么将该元素插入他的后边,然后判断元素个数是否达到8,如果达到8(即插入了第9个元素后),并且table大小达到64,就转为红黑树。如果下一个元素不为空,就判断当前元素和链表下一个元素是否是同一个对象或者equals后是否相等,如果相等,插入失败,否则继续往后查询



HashSet扩容机制和转换红黑树机制

1、HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16 × 加载因子(loadFactor = 0.75) = 12

2、如果table数组使用到了临界值12,就会扩容到 16 × 2 = 32,新的临界值就是32 × 0.75 = 24,以此类推。不管新增加的元素是增加到数组上,还是链表上,都要size++,size大于临界值就扩容。

3、在Java8中,如果一条链表的元素个数达到TREEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍采用数组扩容机制。

package com.level7.Set_;

import java.util.HashSet;
import java.util.Objects;

public class HashSource03 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
//        for(int i = 1; i <= 100; i++) {
//            hashSet.add(i);
//        }

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

class A {
    private int n;

    public A(int n) {
        this.n = n;
    }

    @Override
    public int hashCode() {
        return 10;
    }
}


HashSet练习

  1. 定义一个Employee对象放入HashSet

  2. 当name和age的值相同时,认为是相同员工,不能添加到HashSet中

必须重写Employee的equals和hashCode方法,如果不重写hashCode方法,它他们的hash值是不同的就可以加入重复元素了

package com.level7.Set_;

import java.util.HashSet;
import java.util.Objects;

public class HashSetExercise01 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee("xrj", 20));
        hashSet.add(new Employee("xrj", 20));
        hashSet.add(new Employee("xyy", 10));
        hashSet.add(new Employee("xyy", 10));
        hashSet.add(new Employee("x1", 30));
        hashSet.add(new Employee("x1", 30));
        hashSet.add(new Employee("x2", 40));
        hashSet.add(new Employee("x2", 40));
        System.out.println(hashSet);
    }
}

class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = 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);
    }

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


三、LinkedHashSet

1、LinkedHashSetHashSet的子类

2、LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组 + 链表 + 红黑树 + 双向链表

3、LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用双向链表来维护元素的次序

4、LinkedHashSet不允许添加重复元素

在这里插入图片描述


底层源码分析

package com.level7.Set_;

import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetSource {
    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        set.add(new String("xrj"));
        set.add(123);
        set.add(123);
        set.add(new People("xyy", 20));
        set.add(666);
        set.add("AAA");
        // 输出顺序和插入顺序是相同的
        System.out.println(set);
    }
}

class People {
    public String name;
    public int age;

    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

1、Set set = new LinkedHashSet();执行这条语句,实际创建的是LinkedHashMap,它是HashMap的子类


2、set.add(new String("xrj"));执行add方法,和HashSet一样,先执行put方法,然后计算完key的hash值,执行putVal方法,最后进入putVal方法执行

在这里插入图片描述

HashSet不同点在于,这里newNode调用的是LinkedHashMapnewNode方法

在这里插入图片描述
在这里插入图片描述

LinkedHashMapnewNode方法创建的是一个Entry,它是Node的子类,它当中有beforeafter节点,返回值类型为Node相当于向上转型。然后执行linkNodeLast(p); 该方法会使用beforeafter节点将这次加入的和上次加入的值连接在一起

在这里插入图片描述

3、HashSet加入的是Node节点,而LinkedHashSet加入的是Entry节点

在这里插入图片描述

EntryHashMap.Node的子类,它里面多了beforeafter属性,分别代表他的前一个节点,和后一个节点,以此形成双向链表

在table表中可以看到,第一次添加的是xrj他在1号位置,他的before为空,因为他是第一个节点。

第二次添加的是123,可以看到xrjafter节点存的是123的位置,而123before节点存的是xrj的位置。

因为他们两个的单链表都只有一个元素,因此next为空

在这里插入图片描述

LinkedHashMap还有一个headtail节点,存储第一个元素地址和最后一个元素地址,所以遍历的时候会按插入元素的顺序输出

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值