ToB企服应用市场:ToB评测及商务社交产业平台

标题: HashSet 添加/遍历元素源码分析 [打印本页]

作者: 嚴華    时间: 2022-8-24 14:02
标题: HashSet 添加/遍历元素源码分析
HashSet 类图


HashSet 简单说明

  1. public HashSet() {
  2.         map = new HashMap<>();
  3. }
复制代码
HashSet 底层机制说明

HashSet 底层是 HashMap,HashMap 底层是 数组 + 链表 + 红黑树
模拟数组+链表的结构

  1. /**
  2. * 模拟 HashSet 数组+链表的结构
  3. */
  4. public class HashSetStructureMain {
  5.     public static void main(String[] args) {
  6.         // 模拟一个 HashSet(HashMap) 的底层结构
  7.         // 1. 创建一个数组,数组的类型为 Node[]
  8.         // 2. 有些地方直接把 Node[] 数组称为 表
  9.         Node[] table = new Node[16];
  10.         System.out.println(table);
  11.         // 3. 创建节点
  12.         Node john = new Node("john", null);
  13.         table[2] = jhon; // 把节点 john 放在数组索引为 2 的位置
  14.         Node jack = new Node("jack", null);
  15.         jhon.next = jack; // 将 jack 挂载到 jhon 的后面
  16.         Node rose = new Node("rose", null);
  17.         jack.next = rose; // 将 rose 挂载到 jack 的后面
  18.         Node lucy = new Node("lucy", null);
  19.         table[3] = lucy; // 将 lucy 放在数组索引为 3 的位置
  20.         System.out.println(table);
  21.     }
  22. }
  23. // 节点类 存储数据,可以指向下一个节点,从而形成链表
  24. class Node{
  25.     Object item; // 存放数据
  26.     Node next; // 指向下一个节点
  27.     public Node(Object item, Node next){
  28.         this.item = item;
  29.         this.next = next;
  30.     }
  31. }
复制代码
HashSet 添加元素底层机制

HashSet 添加元素的底层实现

HashSet 扩容机制

HashSet 添加元素源码
  1. /**
  2. * HashSet 源码分析
  3. */
  4. public class HashSetSourceMain {
  5.     public static void main(String[] args) {
  6.         HashSet hashSet = new HashSet();
  7.         hashSet.add("java");
  8.         hashSet.add("php");
  9.         hashSet.add("java");
  10.         System.out.println("set = " + hashSet);
  11.         // 源码分析
  12.         // 1. 执行 HashSet()
  13.         /**
  14.          * public HashSet() { // HashSet 底层是 HashMap
  15.          *         map = new HashMap<>();
  16.          *     }
  17.          */
  18.         // 2. 执行 add()
  19.         /**
  20.          * public boolean add(E e) { // e == "java"
  21.          *         // HashSet 的 add() 方法其实是调用 HashMap 的 put()方法
  22.          *         return map.put(e, PRESENT)==null; // (static) PRESENT = new Object(); 用于占位
  23.          *     }
  24.          */
  25.         // 3. 执行 put()
  26.         // hash(key) 得到 key(待存元素) 对应的hash值,并不等于 hashcode()
  27.         // 算法是 h = key.hashCode()) ^ (h >>> 16
  28.         /**
  29.          * public V put(K key, V value) {
  30.          *         return putVal(hash(key), key, value, false, true);
  31.          *     }
  32.          */
  33.         // 4. 执行 putVal()
  34.         // 定义的辅助变量:Node<K,V>[] tab; Node<K,V> p; int n, i;
  35.         // table 是 HashMap 的一个属性,初始化为 null;transient Node<K,V>[] table;
  36.         // resize() 方法,为 table 数组指定容量
  37.         // p = tab[i = (n - 1) & hash] 计算 key的hash值所对应的 table 中的索引位置,将索引位置对应的 Node 赋给 p
  38.         /**
  39.          * final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  40.          *                    boolean evict) {
  41.          *         Node<K,V>[] tab; Node<K,V> p; int n, i; // 辅助变量
  42.          *         // table 就是 HashMap 的一个属性,类型是 Node[]
  43.          *         // if 语句表示如果当前 table 是 null,或者 table.length == 0
  44.          *         // 就是 table 第一次扩容,容量为 16
  45.          *         if ((tab = table) == null || (n = tab.length) == 0)
  46.          *             n = (tab = resize()).length;
  47.          *         // 1. 根据 key,得到 hash 去计算key应该存放到 table 的哪个索引位置
  48.          *         // 2. 并且把这个位置的索引值赋给 i;索引值对应的元素,赋给 p
  49.          *         // 3. 判断 p 是否为 null
  50.          *         // 3.1 如果 p 为 null,表示还没有存放过元素,就创建一个Node(key="java",value=PRESENT),并把这个元素放到 i 的索引位置
  51.          *         // tab[i] = newNode(hash, key, value, null);
  52.          *         if ((p = tab[i = (n - 1) & hash]) == null)
  53.          *             tab[i] = newNode(hash, key, value, null);
  54.          *         else {
  55.          *             Node<K,V> e; K k; // 辅助变量
  56.          *             // 如果当前索引位置对应的链表的第一个元素和待添加的元素的 hash值一样
  57.          *             // 并且满足下面两个条件之一:
  58.          *             // 1. 待添加的 key 与 p 所指向的 Node 节点的key 是同一个对象
  59.          *             // 2. 待添加的 key.equals(p 指向的 Node 节点的 key) == true
  60.          *             // 就认为当前待添加的元素是重复元素,添加失败
  61.          *             if (p.hash == hash &&
  62.          *                 ((k = p.key) == key || (key != null && key.equals(k))))
  63.          *                 e = p;
  64.          *             // 判断 当前 p 是不是一颗红黑树
  65.          *             // 如果是一颗红黑树,就调用 putTreeVal,来进行添加
  66.          *             else if (p instanceof TreeNode)
  67.          *                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  68.          *             else {
  69.          *                  // 如果 当前索引位置已经形成一个 链表,就使用 for 循环比较
  70.          *                  // 将待添加元素依次和链表上的每个元素进行比较
  71.          *                  // 1. 比较过程中如果出现待添加元素和链表中的元素有相同的,比较结束,出现重复元素,添加失败
  72.          *                  // 2. 如果比较到链表最后一个元素,链表中都没出现与待添加元素相同的,就把当前待添加元素放到该链表最后的位置
  73.          *                  // 注意:在把待添加元素添加到链表后,立即判断 该链表是否已经到达 8 个节点
  74.          *                  // 如果到达,就调用 treeifyBin() 对当前这个链表进行数化(转成红黑树)
  75.          *                  // 注意:在转成红黑树前,还要进行判断
  76.          *                  // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  77.          *                  //        resize();
  78.          *                  // 如果上面条件成立,先对 table 进行扩容
  79.          *                  // 如果上面条件不成立,才转成红黑树
  80.          *                 for (int binCount = 0; ; ++binCount) {
  81.          *                     if ((e = p.next) == null) {
  82.          *                         p.next = newNode(hash, key, value, null);
  83.          *                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  84.          *                             treeifyBin(tab, hash);
  85.          *                         break;
  86.          *                     }
  87.          *                     if (e.hash == hash &&
  88.          *                         ((k = e.key) == key || (key != null && key.equals(k))))
  89.          *                         break;
  90.          *                     p = e;
  91.          *                 }
  92.          *             }
  93.          *             // e 不为 null ,说明添加失败
  94.          *             if (e != null) { // existing mapping for key
  95.          *                 V oldValue = e.value;
  96.          *                 if (!onlyIfAbsent || oldValue == null)
  97.          *                     e.value = value;
  98.          *                 afterNodeAccess(e);
  99.          *                 return oldValue;
  100.          *             }
  101.          *         }
  102.          *         ++modCount;
  103.          *         // 扩容:说明判断 table 是否扩容不是看 table 的length
  104.          *         // 而是看 整个 HashMap 的 size(即已存元素个数)
  105.          *         if (++size > threshold)
  106.          *             resize();
  107.          *         afterNodeInsertion(evict);
  108.          *         return null;
  109.          *     }
  110.          */
  111.     }
  112. }
复制代码
HashSet 遍历元素底层机制

HashSet 遍历元素底层机制

  1. public Iterator<E> iterator() {
  2.     return map.keySet().iterator();
  3. }
复制代码
  1. public Set<K> keySet() {
  2.     Set<K> ks = keySet;
  3.     if (ks == null) {
  4.         ks = new KeySet();
  5.         keySet = ks;
  6.     }
  7.     return ks;
  8. }
复制代码
  1. public final Iterator<K> iterator()     { return new KeyIterator(); }
复制代码
  1. final class KeyIterator extends HashIterator
  2.     implements Iterator<K> {
  3.     public final K next() { return nextNode().key; }
  4. }
复制代码
HashSet 遍历元素源码
  1. /** * HashSet 源码分析 */public class HashSetSourceMain {    public static void main(String[] args) {        HashSet hashSet = new HashSet();        hashSet.add("java");        hashSet.add("php");        hashSet.add("java");        System.out.println("set = " + hashSet);        // HashSet 迭代器实现原理        // HashSet 底层是 HashMap,HashMap 底层是 数组 + 链表 + 红黑树        // HashSet 本身没有实现迭代器,而是借由 HashMap 来实现的        // 1. hashSet.iterator() 实际上是去调用 HashMap 的 keySet().iterator()        /**         * public Iterator iterator() {         *         return map.keySet().iterator();         *     }         */        // 2. KeySet() 方法返回一个 KeySet 对象,而 KeySet 是 HashMap 的一个内部类        /**         * public Set keySet() {         *         Set ks = keySet;         *         if (ks == null) {         *             ks = new KeySet();         *             keySet = ks;         *         }         *         return ks;         *     }         */        // 3. KeySet().iterator() 方法返回一个 KeyIterator 对象,KeyIterator 是 HashMap的一个内部类        /**         * public final Iterator<K> iterator()     { return new KeyIterator(); }         */        // 4. KeyIterator 继承了 HashIterator(HashMap的内部类) 类,并实现了 Iterator 接口        // 即 KeyIterator、HashIterator 才是真正实现 迭代器的类        /**         * final class KeyIterator extends HashIterator         *         implements Iterator {         *         public final K next() { return nextNode().key; }         *     }         */        // 5. 当执行完 Iterator iterator = hashSet.iterator(); 后        // 此时的 iterator 对象中已经存储了一个元素节点        // 怎么做到的?        // 回到第 3 步,KeySet().iterator() 方法返回一个 KeyIterator 对象        // new KeyIterator() 调用 KeyIterator 的无参构造器        // 在这之前,会先调用 KeyIterator 父类 HashIterator 的无参构造器        // 因此分析 HashIterator 的无参构造器就知道发生了什么        /**         *         Node next;        // next entry to return         *         Node current;     // current entry         *         int expectedModCount;  // for fast-fail         *         int index;             // current slot         * HashIterator() {         *             expectedModCount = modCount;         *             Node[] t = table;         *             current = next = null;         *             index = 0;         *             if (t != null && size > 0) { // advance to first entry         *                 do {} while (index < t.length && (next = t[index++]) == null);         *             }         *         }         */        // 5.0 next, current, index 都是 HashIterator 的属性        // 5.1 Node[] t = table; 先把 Node 数组 table 赋给 t        // 5.2 current = next = null; 把 current 和 next 都置为 null        // 5.3 index = 0; index 置为 0        // 5.4 do {} while (index < t.length && (next = t[index++]) == null);        // 这个 do{} while 循环会在 table 中 遍历 Node节点        // 一旦 (next = t[index++]) == null 不成立时,就说明找到了一个 table 中的节点        // 将这个节点赋给 next,并退出当前 do while循环        // 此时 Iterator iterator = hashSet.iterator(); 就执行完了        // 当前 iterator 的运行类型其实是 HashIterator,而 HashIterator 的 next 中存储着从 table 中遍历出来的一个 Node节点        // 6. 执行 iterator.hasNext()        /**         * public final boolean hasNext() {         *             return next != null;         *         }         */        // 6.1 此时的 next 存储着一个 Node,所以并不为 null,返回 true        // 7. 执行 iterator.next(),其实是去执行 HashIterator 的 nextNode()        /**         * final Node nextNode() {         *             Node[] t;         *             Node e = next;         *             if (modCount != expectedModCount)         *                 throw new ConcurrentModificationException();         *             if (e == null)         *                 throw new NoSuchElementException();         *             if ((next = (current = e).next) == null && (t = table) != null) {         *                 do {} while (index < t.length && (next = t[index++]) == null);         *             }         *             return e;         *         }         */        // 7.1 Node e = next; 把当前存储着 Node 节点的 next 赋值给了 e        // 7.2 (next = (current = e).next) == null        // 判断当前节点的下一个节点是否为 null        // a. 如果当前节点的下一个节点为 null        // 就执行 do {} while (index < t.length && (next = t[index++]) == null);        // 再 table 数组中 遍历,寻找 table 数组中的下一个 Node 并赋值给 next        // b. 如果当前节点的下一个节点不为 null        // 就将当前节点的下一个节点赋值给 next,并且此刻不会去 table 数组中遍历下一个 Node 节点        // 7.3 将找到的节点 e 返回        // 7.4 之后每次执行 iterator.next(),都像 a、b 那样去判断遍历,直到遍历完成        Iterator iterator = hashSet.iterator();        while (iterator.hasNext()) {            Object next =  iterator.next();            System.out.println(next);        }    }}
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4