HashSet机制说明

基本说明

  1. HashSet实现类Set接口
  2. HashSet实际上是HashMap,源码:public HashSet() {map = new HashMap<>();}
  3. HashMap底层是数组+链表+红黑树。
  4. 可以存放null值,但是只能存放一个null
  5. HashSet不保证元素的顺序,取决于hash后,再确定索引的结果
  6. 同Set接口一样不能有重复元素。
数组链表模拟

public class HashSetStructure {
    // 模拟HashMap底层
    public static void main(String[] args) {
        //创建一个Node类型的数组(表)
        Node[] table=new Node[16];// 断点1
        System.out.println(table);
        Node jack = new Node("jack", null);
        table[2]=jack;// 断点2
        Node rick = new Node("rick",null);
        // 把rick这个节点挂载到jack
        jack.next=rick;
        Node rose=new Node("Rose",null);
        // 把rose这个节点挂载到jack
        rick.next=rose;
        Node alicy=new Node("alicy",null);
        // 把alicy放到table表中索引为3的位置。
        table[3]=alicy;
        System.out.println(table);

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

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

    }
}
  1. 添加一个元素时,先获取hash值(hashCode方法),会转成索引值。
  2. 找到存储数据表table,看这个索引位置是否已存在元素
  3. 若无直接存入,若有则调用equals该方法由程序员定义)比较,比较后相同放弃添加;不同则添加至最后
  4. 在jdk8中,若一条链表的元素个数到达TREEIFY_THRESHOLD(默认为8),且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
源码解析
       /*
      // 1.
      public HashSet() {
        map = new HashMap<>();
      }
       */
      HashSet hashSet = new HashSet();
      /*
      //2. 执行add方法
      public boolean add(E e) {
      // PRESENT来自HashSet源码:private static final Object PRESENT = new Object();
        return map.put(e, PRESENT)==null;
      }
      //3.执行put方法,执行hash(key)获得hash值:(h = key.hashCode()) ^ (h >>> 16)
         public V put(K key, V value) {   // key="hello" value=PRESENT(共享)
            return putVal(hash(key), key, value, false, true);
         }
       //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;
        // 根据key得到的hash计算存放位置,同时把该位置的对象赋给p
        // 判断p是否为null ,若p为null表示没有存放元素,就创建一个Node(key="hello",value=PRESENT)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
        // 辅助变量
            Node<K,V> e; K k;
            // 若当前索引位置对应链表的第一个元素和准备添加的key的hash值一样
            // 并且满足:准备加入的key和p指向的Node节点的key是同一个对象 或 指向Node节点的key的equals()和准备加入的key比较后相同
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 判断是否为红黑树,是则调用putTreeVal进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // 若table对应索引位置,
            else {
                // 已经是一个链表,进行for循环比较后都不相同(准备加入的元素不重复)
                for (int binCount = 0; ; ++binCount) {
                    // 加入链表最后
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 在添加元素后立即判断该链表是否达到8个节点,达到调用treeifyBin() 转为红黑树
                        // 转为红黑树时进行判断,if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();
                        // 成立则table扩容,否则转为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 比较元素相同(重复)直接break退出
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
       */
      hashSet.add("hello");
      hashSet.add("world");
      hashSet.add("hello");
      System.out.println(hashSet);

HashSet扩容与转成红黑树

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

若table数组使用到12就会扩容为16*2=32,新的临界值就是32*0.75=24;以此类推。

      HashSet hashSet = new HashSet();
      for (int i = 0; i <= 100; i++) {    //Debug可以查看扩容机制过程
         hashSet.add(i);
      }

在jdk8中,若一条链表的元素个数到达TREEIFY_THRESHOLD(默认为8),且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

 通过断点Debug可以观察到table的扩容过程与树化。

public class HashSetTest {
   public static void main(String[] args) {
      HashSet hashSet = new HashSet();
      for (int i = 1; i < 12; i++) {    // 断点
         hashSet.add(new N(i));
      }
      System.out.println(hashSet);
   }
}
class N{
   private int id;

   public N(int id) {
      this.id = id;
   }

   @Override
   // 重写返回的hash值改为固定
   public int hashCode() {
      return 999;
   }
}

自定义比较通过idea快捷键【Alt】+【ins】选择equals() and hashcode()重写比较方式

自定义比较示例
 public class HashSetTest {
   public static void main(String[] args) {
      HashSet hashSet = new HashSet();
      hashSet.add(new Employee("jack",18));
      hashSet.add(new Employee("jack",28));
      hashSet.add(new Employee("jack",18));
      hashSet.add(new Employee("rick",18));
      System.out.println(hashSet);
   }
}
class Employee{
   private String name;
   private int age;

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

   public String getName() {
      return name;
   }

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

   public int getAge() {
      return age;
   }

   public void setAge(int age) {
      this.age = age;
   }

   @Override
   public String toString() {
      return "Employee{" +
              "name='" + name + '\'' +
              ", 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);
   }
}

 

posted @ 2024-07-05 16:41  Dr丶云幕  阅读(2)  评论(0编辑  收藏  举报