Set接口,

  1. 接本介绍
    1. 无序(添加和取出的顺序不一致),没有索引
    2. 不允许重负元素,所以最多包含一个null
  2. Set接口的常用方法
    1. 和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection一样
  3. Set接口的遍历方式
    1. 同Collection的遍历方式一样,因为Set接口是Collection接口的子接口
    2. 可以使用迭代器
    3. 增强for
    4. 不能使用索引的方式来获取
    5. package com.jshedu.Set_;
      
      import java.util.HashSet;
      import java.util.Iterator;
      import java.util.Set;
      
      /**
       * @author 韩顺平
       * @version 1.0
       */
      @SuppressWarnings({"all"})
      public class SetMethod {
          public static void main(String[] args) {
              //老韩解读
              //1. 以Set 接口的实现类 HashSet 来讲解Set 接口的方法
              //2. set 接口的实现类的对象(Set接口对象), 不能存放重复的元素, 可以添加一个null
              //3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
              //4. 注意:取出的顺序的顺序虽然不是添加的顺序,但是他是固定的.
              Set set = new HashSet();
              set.add("john");
              set.add("lucy");
              set.add("john");//重复
              set.add("jack");
              set.add("hsp");
              set.add("mary");
              set.add(null);//
              set.add(null);//再次添加null
              for(int i = 0; i <10;i ++) {
                  System.out.println("set=" + set);
              }
      
              //遍历
              //方式1: 使用迭代器
              System.out.println("=====使用迭代器====");
              Iterator iterator = set.iterator();
              while (iterator.hasNext()) {
                  Object obj =  iterator.next();
                  System.out.println("obj=" + obj);
      
              }
      
              set.remove(null);
      
              //方式2: 增强for
              System.out.println("=====增强for====");
      
              for (Object o : set) {
                  System.out.println("o=" + o);
              }
      
              //set 接口对象,不能通过索引来获取
      
      
          }
      }
      

      遍历只有两种方法

  4. package com.jshedu.Set_;
    
    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * @author 韩顺平
     * @version 1.0
     */
    @SuppressWarnings({"all"})
    public class HashSet_ {
        public static void main(String[] args) {
            //老韩解读
            //1. 构造器走的源码
            /*
                public HashSet() {
                    map = new HashMap<>();//实际上是HashMap
                }
             2. HashSet 可以存放null ,但是只能有一个null,即元素或对象不能重复
             3.HashSet不保证元素是有序的,取决于hash后,在确定索引的结果
               (不保证存放顺序和取出顺序一样)
             */
            Set hashSet = new HashSet();
            hashSet.add(null);
            hashSet.add(null);
            System.out.println("hashSet=" + hashSet);
    
    
        }
    }
    

    HashSet讲解

  5. HashSet底层原理

    1. 链表在指定位置添加结点

      package com.jshedu.Set_;
      
      /**
       * @author 韩顺平
       * @version 1.0
       */
      @SuppressWarnings({"all"})
      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;
              //在链表中,分两步
              //1.先创建要加入的结点
              //2.用属性next,把结点的地址赋给它。就把结点连起来了
              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;
          }
      }
      

      在数组的指定位置添加链表形式的数据。

  6. HashSet添加元素底层是如何实现的

    1. HashSet底层是HashMap

    2. 添加一个元素时,先得到hash值-->会转成--->索引值

    3. 找到存储数据表table,看这个索引位置是否已经存放的有元素

    4. 如果没有,直接加入

    5. 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后

    6. 在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)。【如果table的大小还没有到64,但链表的个数超过8,那么table就会扩容,2倍扩容】

    7. package com.jshedu.Set_;
      
      import java.util.HashSet;
      
      /**
       * @author 韩顺平
       * @version 1.0
       */
      @SuppressWarnings({"all"})
      public class HashSetSource {
          public static void main(String[] args) {
      
              HashSet hashSet = new HashSet();
              hashSet.add("java");//到此位置,第1次add分析完毕.
              hashSet.add("php");//到此位置,第2次add分析完毕
              hashSet.add("java");
              System.out.println("set=" + hashSet);
      
              /*
              老韩对HashSet 的源码解读
              1. 执行 HashSet()
                  public HashSet() {
                      map = new HashMap<>();
                  }
              2. 执行 add()
                 public boolean add(E e) {//e = "java"
                      return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
                 }
               3.执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16)
                                                             hash值不是hashCode,是经过处理的hashCode。右移16位
      
                   public V put(K key, V value) {//key = "java" 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[] tab; Node 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)
                      //i = (n - 1) & hash,决定元素放到那个位置
                      if ((p = tab[i = (n - 1) & hash]) == null)//p指向表的一个结点
                          tab[i] = newNode(hash, key, value, null);
                      else {
                          //一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
                          Node 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)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扩容。(就是虽然你的链表有8个元素,但数组还没有到64个)
                                //    只有上面条件不成立时,才进行转成红黑树
                                //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
      
                              for (int binCount = 0; ; ++binCount) {
                                  if ((e = p.next) == null) {
                                      p.next = newNode(hash, key, value, null);
                                      if (binCount >= TREEIFY_THRESHOLD(8) - 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;
                      //size 就是我们每加入一个结点Node(k,v,h,next), size++
                      if (++size > threshold)
                          resize();//扩容
                      afterNodeInsertion(evict);
                      return null;
                  }
               */
      
          }
      }
      

      add

  7. HashSet的扩容和转成红黑树机制

    1. 当一个链表上元素个数是8之后再在该链表上添加元素,会导致table扩容,按扩容机制来扩容,而且链表的hash值决定的table数组下标也会发生改变。链表上在添加,table在扩容,当table大于64的时候,链表会被树化,不在扩容

    2. package com.jshedu.Set_;
      
      import java.util.HashSet;
      import java.util.Objects;
      
      /**
       * @author 韩顺平
       * @version 1.0
       */
      @SuppressWarnings({"all"})
      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++
                  size和扩容有关(不是table达到0.75才扩容,只要链表元素的个数达到0.75也扩容
                  不管table上有几个链表)
               */
      
              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;
          }
      }

      hashCode值是怎么算出来的不用管,内部有一个数学算法,尽量不同的Object数组得到的hashCode不同。

  8. 例题

    1. package com.jshedu.Set_;
      
      import java.util.HashSet;
      import java.util.Objects;
      
      /**
       * @author Mr.jia
       * @version 1.0
       */
      
      public class Homework01 {
          @SuppressWarnings({"all"})
          public static void main(String[] args) {
              HashSet obj = new HashSet();
              obj.add(new Employee("jack",20));
              //在add过程中就用hashCode()方法进行比较name,age
              obj.add(new Employee("tom",22));
              obj.add(new Employee("tom",32));
              obj.add(new Employee("jack",20));//加不进去
              //重写hashCode(),equals()前
              //这里加入四个对象,为什么是四个,第四个和第一个不是一样吗
              //答:创建四个对象,返回的hash值不一样,所以在table表中
              //    存储的位置也就不同
              //重写后会对name,age进行比较
              System.out.println(obj.toString());
      
      
          }
      }
      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);
          }
          //name和age相同就返回相同的hash值
          @Override
          public int hashCode() {
              return Objects.hash(name, age);
              //先比较返回hash值是否相同,相同在比较内容,
              //内容不同就直接放链表尾部
              //相同就不加入
          }
      }
      

      重写hashCode(),和equals()

    2. package com.jshedu.Set_;
      
      import java.util.HashSet;
      import java.util.Objects;
      
      /**
       * @author Mr.jia
       * @version 1.0
       */
      
      public class Homework02 {
          public static void main(String[] args) {
              HashSet objects = new HashSet<>();
              MyDate myDate = new MyDate(1999, 4, 17);
              objects.add(new Employer("jack",10000,myDate));
              MyDate myDate1 = new MyDate(1999, 5, 17);
              objects.add(new Employer("tom",10000,myDate));
              objects.add(new Employer("jack",10000,myDate));
              for (Object o :objects) {
                  System.out.println("Employee:"+o);
              }
      
              //System.out.println(objects);
      
          }
      }
      class Employer{
          private String name;
          private double sal;
          private MyDate birthday;
      //    class MyDate1{
      //        private int year;
      //        private int month;
      //        private int day;
      //    }
      
          public Employer(String name, double sal, MyDate birthday) {
              this.name = name;
              this.sal = sal;
              this.birthday = birthday;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public double getSal() {
              return sal;
          }
      
          public void setSal(double sal) {
              this.sal = sal;
          }
      
          public MyDate getBirthday() {
              return birthday;
          }
      
          public void setBirthday(MyDate birthday) {
              this.birthday = birthday;
          }
      
          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
              if (o == null || getClass() != o.getClass()) return false;
              Employer employer = (Employer) o;
              return Objects.equals(name, employer.name) && Objects.equals(birthday, employer.birthday);
          }
      
          @Override
          public int hashCode() {
              return Objects.hash(name, birthday);
          }
      
          @Override
          public String toString() {
              return "Employer{" +
                      "name='" + name + '\'' +
                      ", sal=" + sal +
                      ", birthday=" + birthday +
                      '}';
          }
      }
      class MyDate{
          private int year;
          private int month;
          private int day;
      
          public MyDate(int year, int month, int day) {
              this.year = year;
              this.month = month;
              this.day = day;
          }
      
          public int getYear() {
              return year;
          }
      
          public void setYear(int year) {
              this.year = year;
          }
      
          public int getMonth() {
              return month;
          }
      
          public void setMonth(int month) {
              this.month = month;
          }
      
          public int getDay() {
              return day;
          }
      
          public void setDay(int day) {
              this.day = day;
          }
      }
       

      参数是引用类型怎么处理myDate

      你可能感兴趣的:(java,数据结构,jvm)