Java--集合--HashSet

  1. HashSet的全面
    1. HashSet的底层是HashSet,
    2. 执行add方法之后,或返回一个boolean值,如果添加成功则返回true,如果添加失败,则返回false
    3. package com.model.collection.set.hashset;
      
      import java.util.HashSet;
      
      /**
       * @Description:测试类
       * @Author: 张紫韩
       * @Crete 2021/6/12 15:56
       */
      public class HashSetDemo01 {
          public static void main(String[] args) {
              HashSet<Object> set = new HashSet<>();
      
              /**
               *
               * HashSet:无顺序,不可以添加重复的值,可以有null,但是只能由一个null
               * 1.当执行add添加后会返回一个 boolean,如过插入成功则放回true反正则为false
               * 2.可以使用remove指定删除对象
               * */
              System.out.println(set.add("a")); //T
              System.out.println(set.add("b"));//T
              System.out.println(set.add("c"));//T
              System.out.println(set.add("d"));//T
              System.out.println(set.add("a"));//F
              System.out.println(set.remove("d"));//T
              System.out.println(set);
      
      
              //在加深一下,非常经典的面试题
              //查看源码,做分析,
              set=new HashSet<>();
              System.out.println(set);
              set.add("lose");//T
              set.add("lose");//F
              set.add(new Dog("tom"));//T
              set.add(new Dog("tom"));//T
              set.add(new String("abc"));//T
              set.add(new String("abc"));//F
              System.out.println(set);
      
      
          }
      }
      class Dog{
          private String name;
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          @Override
          public String toString() {
              return "Dog{" +
                      "name='" + name + '\'' +
                      '}';
          }
      
          public Dog(String name) {
              this.name = name;
          }
      }
  2. 底层机制数组链表模拟
    1. package com.model.collection.set.hashset;
      
      import java.util.HashSet;
      
      /**
       * @Description:测试类
       * @Author: 张紫韩
       * @Crete 2021/6/12 16:25
       */
      public class HashSetDemo02 {
          //模拟一个HashSet底层(HashMop底层结构)(数组+链表+红黑树)
          public static void main(String[] args) {
      
      //        HashSet<Object> hashSet = new HashSet<>();
              Node[] table=new Node[16];
              Node jack=new Node("jack", null);
              table[2]=jack;
              Node rose=new Node("rose", null);
              jack.setNext(rose);
              Node tom=new Node("tom", null);
              rose.setNext(tom);
              Node join=new Node("join", null);
              tom.setNext(join);
              System.out.println(table);
          }
      }
      class Node{
      
          private Object item;
          private Node next;
      
          public Node(Object item, Node next) {
              this.item = item;
              this.next = next;
          }
      
          public Object getItem() {
              return item;
          }
      
          @Override
          public String toString() {
              return "Node{" +
                      "item=" + item +
                      ", next=" + next +
                      '}';
          }
      
          public void setItem(Object item) {
              this.item = item;
          }
      
          public Node getNext() {
              return next;
          }
      
          public void setNext(Node next) {
              this.next = next;
          }
      }
  3. 源码解析
    1. 结论:
      1.  

         

    2.  解读案例

      1. 执行 new HashSet()

        1. public HashSet() {
                  map = new HashMap<>();
              }
      2. 执行add()方法

        1. public boolean add(E e) {
                  return map.put(e, PRESENT)==null;
              }
      3. 调用执行map.put() 方法(key是传入的e,value是present,是一个常量):

        1. public V put(K key, V value) {
                  return putVal(hash(key), key, value, false, true);
              }
      4. 执行hash(key)计算key的hash值

      5. 执行 putVal(hash(key), key, value, false, true)方法

        1. //第一次添加元素时: hashSet.add("java");
          final
          V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
          //创建辅助变量  Node
          <K,V>[] tab; Node<K,V> p; int n, i;
          //判断当前的 表 是否为空(第一次添加),如果时第一次添加元素,则进行扩容,扩容的默认长度是16
          if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;
          //通过哈希值获取索引,找到这个元素应该插入的位置节点,并赋值给p,同时判断这个节点上是否为null,如果我这个节点为null,则表示这个节点上没有存放元素,直接将这个元素放在这个节点上,否则放在链表的最后面
          if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); 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; }
      6. 第二次添加hashSet.add("php");
        1. //第二次添加元素时:hashSet.add("php")
          final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                             boolean evict) {
          //创建辅助变量 
                  Node<K,V>[] tab; Node<K,V> p; int n, i; 
          //判断当前的 表 是否为空(第一次添加),如果时第一次添加元素,则进行扩容,扩容的默认长度是16(第二次添加表不为空,则不进行扩容)
                  if ((tab = table) == null || (n = tab.length) == 0)
                      n = (tab = resize()).length;  
          //通过哈希值获取索引,找到这个元素应该插入的位置节点,并赋值给p,同时判断这个节点上是否为null,如果我这个节点为null,则表示这个节点上没有存放元素,直接将这个元素放在这个节点上,否则放在链表的最后面
                  if ((p = tab[i = (n - 1) & hash]) == null)
                      tab[i] = newNode(hash, key, value, null);
                  else {
                      Node<K,V> e; K k;
                      if (p.hash == hash &&
                          ((k = p.key) == key || (key != null && key.equals(k))))
                          e = p;
                      else if (p instanceof TreeNode)
                          e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                      else {
                          for (int binCount = 0; ; ++binCount) {
                              if ((e = p.next) == null) {
                                  p.next = newNode(hash, key, value, null);
                                  if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                      treeifyBin(tab, hash);
                                  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;
              }
      7. 第三次添加元素 hashSet.add("java");
        1. //第一次添加元素时: hashSet.add("java");
          final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                             boolean evict) {
          //创建辅助变量 
                  Node<K,V>[] tab; Node<K,V> p; int n, i; 
          //判断当前的 表 是否为空(第一次添加),如果时第一次添加元素,则进行扩容,扩容的默认长度是16,这是第三次所以不会进入
                  if ((tab = table) == null || (n = tab.length) == 0)
                      n = (tab = resize()).length;  
          //通过哈希值获取索引,找到这个元素应该插入的位置节点,并赋值给p,同时判断这个节点上是否为null,如果我这个节点为null,则表示这个节点上没有存放元素,直接将这个元素放在这个节点上,否则放在链表的最后面
          //已经由一个java 放在了索引为3 的地方,所以现在的 p执行的是 第一个java节点,不为空,不会进入,则会进入到else中 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else {
          //一个开发技巧:在需要局部变量的时候在定义 Node
          <K,V> e; K k;
          //判断p(链表第一个节点)要插入的元素的hash值是否一样,且(他俩的值也一样或者 自定义的 equals方法一样),如果都一样就不能加入到链表中
          //注意:equals():并不是单纯的值相等,这个equals方法我们可以自定义,(String类自定义的equals判断的是值是否相同),我们对其他的类的equals方法可以自定义,(姓名和年龄相同则对象相同)
          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
          else {
          //循环判断链表中所有的节点是否和要添加的节点的值一样且hash值也一样,如果都一样就break不能添加,如果到和链表得元素都不一样就添加到链表的最后
          for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) {//循环判断,e始终指向p的next,p逐渐向 后移动,e也向后移动,达到遍历整个链表 p.next = newNode(hash, key, value, null);
          //添加节点之后,判断节点数量是否到了需要转化成红黑树的数量了 (节点数量为8个的时候)
          if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
          //判断当前链表是否可以进行链表转化为红黑树(树化),条件是:节点数到达8个即以上,且 table的数组的个数为64个
          //如果节点数量到达了8个但table并没有到达64,会对table进行扩容,不会进化为红黑树
          treeifyBin(tab, hash); 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; //判断节点数加一后,是否查过需要扩容的数量,size就是我们加入的节点的数量,当size的数量超过临界值就会进行扩容,不是占用了table表的12个位置才会进行扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
        2. 树化条件判断:(树化条件判断,如果表为空或者长度未达到64则先进行table扩容,不会进行树化)

  4.  总结:

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

    2. 如果table数组到达了临界值12 ,就会扩容到 16*2=32,新的临界值就是32*0.75=24,以此类推

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

    4. 数组扩容机制:

      1. 默认容量为16,临界值为12,在加入13个时就会进行扩容

      2. 扩容到16的二倍32,此时临界值也就变成了24,当加载第25个元素时HashSet就继续进行扩容到64

    5.  链表树化为红黑树

      1. 当一个链表数量超过八个时就会进行扩容判断,默认情况下HashSet的容量时16,当加入第9个元素后,由于表的容量为达到64所以,会先扩容数组,不会进行树化,会对数组进行扩容(树化的条件时表的容量到64且链表的长度超过8),

      2. 此时会变成,链表长度为9个,数组长度为32,

      3. 再次向链表中加入元素,由于数组长度为达到 64选择再次扩容数组,变成了 链表长度10,数组长度64,

      4. 当再次向这个链表接入节点时,判断 数组长度到了64个且链表长度大于八个,进行了树化 

  5. HashSet课堂练习

    1.  

       

    2.  

       

    3.  

    4.  

    5.  

    6. package com.model.collection.set.hashset;
      
      import java.util.HashSet;
      import java.util.Objects;
      
      /**
       * @Description:测试类
       * @Author: 张紫韩
       * @Crete 2021/6/13 15:26
       */
      public class HashSetDemo05 {
          public static void main(String[] args) {
      
              HashSet<Employee> hashSet = new HashSet<>();
              hashSet.add(new Employee("a", 18));
              hashSet.add(new Employee("b", 18));
              hashSet.add(new Employee("c", 18));
              hashSet.add(new Employee("c", 18));  //只加入了一个 c
      
              System.out.println(hashSet);
          }
      }
      class Employee{
          private String name;
          private int age;
      
          public Employee(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          //设置 :两个Employee对象,的name和age相同就返回相同的hash值,且使用equals方法返回true
      
      
          @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 +
                      '}';
          }
      
          public int getAge() {
              return age;
          }
      
          public void setAge(int age) {
              this.age = age;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      }

       

       

    7.  

       

    8. package com.model.collection.set.hashset;
      
      import java.util.HashSet;
      import java.util.Objects;
      
      /**
       * @Description:测试类
       * @Author: 张紫韩
       * @Crete 2021/6/13 15:52
       */
      public class HashSetDemo06 {
          public static void main(String[] args) {
      
              HashSet<Object> hashSet = new HashSet<>();
             hashSet.add(new Person("a", 19, new MyDate("1", "1", "1")));//T
             hashSet.add(new Person("b", 19, new MyDate("1", "1", "1")));//T
             hashSet.add(new Person("a", 18, new MyDate("1", "1", "1")));//F
             hashSet.add(new Person("a", 19, new MyDate("2", "1", "1")));//T
             hashSet.add(new Person("c", 18, new MyDate("2", "1", "1")));//T
      
      
              System.out.println(hashSet);
      
          }
      }
      class Person{
          //当姓名了日期相同时就认为是相同的员工就不能加入到table中
          private String name;
          private int sal;
          private MyDate myDate;
      
          public Person(String name, int sal, MyDate myDate) {
              this.name = name;
              this.sal = sal;
              this.myDate = myDate;
          }
      
          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
              if (o == null || getClass() != o.getClass()) return false;
              Person person = (Person) o;
              return Objects.equals(name, person.name) && Objects.equals(myDate, person.myDate);
          }
      
          @Override
          public int hashCode() {
              return Objects.hash(name, myDate);
          }
      
          @Override
          public String toString() {
              return "Person{" +
                      "name='" + name + '\'' +
                      ", sal=" + sal +
                      ", myDate=" + myDate +
                      '}';
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public int getSal() {
              return sal;
          }
      
          public void setSal(int sal) {
              this.sal = sal;
          }
      
          public MyDate getMyDate() {
              return myDate;
          }
      
          public void setMyDate(MyDate myDate) {
              this.myDate = myDate;
          }
      }
      class MyDate{
          private String year;
          private String month;
          private String day;
      
          public MyDate(String year, String month, String day) {
              this.year = year;
              this.month = month;
              this.day = day;
          }
      
          public String getYear() {
              return year;
          }
      
          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
              if (o == null || getClass() != o.getClass()) return false;
              MyDate myDate = (MyDate) o;
              return Objects.equals(year, myDate.year) && Objects.equals(month, myDate.month) && Objects.equals(day, myDate.day);
          }
      
          @Override
          public int hashCode() {
              return Objects.hash(year, month, day);
          }
      
          @Override
          public String toString() {
              return "MyDate{" +
                      "year='" + year + '\'' +
                      ", month='" + month + '\'' +
                      ", day='" + day + '\'' +
                      '}';
          }
      
          public void setYear(String year) {
              this.year = year;
          }
      
          public String getMonth() {
              return month;
          }
      
          public void setMonth(String month) {
              this.month = month;
          }
      
          public String getDay() {
              return day;
          }
      
          public void setDay(String day) {
              this.day = day;
          }
      }

       

       

       

         

        

       

       

       

posted @ 2021-06-13 16:03  张紫韩  阅读(73)  评论(1编辑  收藏  举报