• HashMap之KeySet分析


    本篇涵盖

    1、HashMap并不是用keySet来存储key的原因及证明

    2、keySet方法返回后的remove、add操作原理

    一、方法作用

     概括一下

    1、keySet方法返回map中包含的键的集合视图

    2、集合由map支持,改变集合会影响map,反之亦然

    3、集合支持删除操作,不支持添加

    二、原理分析

    1、HashMap源码分析

    keySet方法查看keySet是否为null

    不为null则直接返回,若为null则创建后返回

    接下来看构造函数中做了什么

    /**
         * {@inheritDoc}
         *
         * @implSpec
         * This implementation returns a set that subclasses {@link AbstractSet}.
         * The subclass's iterator method returns a "wrapper object" over this
         * map's <tt>entrySet()</tt> iterator.  The <tt>size</tt> method
         * delegates to this map's <tt>size</tt> method and the
         * <tt>contains</tt> method delegates to this map's
         * <tt>containsKey</tt> method.
         *
         * <p>The set is created the first time this method is called,
         * and returned in response to all subsequent calls.  No synchronization
         * is performed, so there is a slight chance that multiple calls to this
         * method will not all return the same set.
         */
        public Set<K> keySet() {
            Set<K> ks = keySet;
            if (ks == null) {
                ks = new AbstractSet<K>() {
                    public Iterator<K> iterator() {
                        return new Iterator<K>() {
                            private Iterator<Entry<K,V>> i = entrySet().iterator();
    
                            public boolean hasNext() {
                                return i.hasNext();
                            }
    
                            public K next() {
                                return i.next().getKey();
                            }
    
                            public void remove() {
                                i.remove();
                            }
                        };
                    }
    
                    public int size() {
                        return AbstractMap.this.size();
                    }
    
                    public boolean isEmpty() {
                        return AbstractMap.this.isEmpty();
                    }
    
                    public void clear() {
                        AbstractMap.this.clear();
                    }
    
                    public boolean contains(Object k) {
                        return AbstractMap.this.containsKey(k);
                    }
                };
                keySet = ks;
            }
            return ks;
        }
    keySet构造函数

    代码注释中提到,创建的对象是AbstractSet的子类

    并且说明了keySet集合是在第一次调用此方法时创建的

    再来看KeySet这个类

    final class KeySet extends AbstractSet<K> {
            public final int size()                 { return size; }
            public final void clear()               { HashMap.this.clear(); }
            public final Iterator<K> iterator()     { return new KeyIterator(); }
            public final boolean contains(Object o) { return containsKey(o); }
            public final boolean remove(Object key) {
                return removeNode(hash(key), key, null, false, true) != null;
            }
            public final Spliterator<K> spliterator() {
                return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
            }
            public final void forEach(Consumer<? super K> action) {
                Node<K,V>[] tab;
                if (action == null)
                    throw new NullPointerException();
                if (size > 0 && (tab = table) != null) {
                    int mc = modCount;
                    for (int i = 0; i < tab.length; ++i) {
                        for (Node<K,V> e = tab[i]; e != null; e = e.next)
                            action.accept(e.key);
                    }
                    if (modCount != mc)
                        throw new ConcurrentModificationException();
                }
            }
        }
    KeySet

    里面包含了clear、remove、forEach等方法

    需要注意,这里不存在任何能存放键的数据结构

    那keySet集合是怎样拿到所有键的呢?不要着急,我们进入父类查看

    2、AbstractSet分析(removeAll实现)

     

     这是一个抽象类,还是没有任何存储结构

    不过我们找到了removeAll方法

    可以看到需要传入一个collection

    利用迭代器遍历,通过remove方法实现删除

    按照这种思想,keySet方法会不会也是利用了迭代器来获取key?

    我们继续进入父类

    3、AbstractCollection分析(add抛异常原因)

     

    还是不存在任何存储结构

    因为实现了collection接口,所以有迭代方法

    我们还看到了add和addAll方法

     

    使用add时直接抛出不支持异常

    addAll调用add,所以还是会报异常

    到这里我们知道add抛异常的出处了,是在AbstractCollection中规定的


    4、KeyIterator迭代器分析

    到此我们没有发现任何的存储结构

    接下来来验证keySet方法是利用了迭代器来获取key的

    使用了KeyIterator迭代器

    进入父类

    abstract class HashIterator {
            Node<K,V> next;        // next entry to return
            Node<K,V> current;     // current entry
            int expectedModCount;  // for fast-fail
            int index;             // current slot
    
            HashIterator() {
                expectedModCount = modCount;
                Node<K,V>[] 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);
                }
            }
    
            public final boolean hasNext() {
                return next != null;
            }
    
            final Node<K,V> nextNode() {
                Node<K,V>[] t;
                Node<K,V> 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;
            }
    
            public final void remove() {
                Node<K,V> p = current;
                if (p == null)
                    throw new IllegalStateException();
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                current = null;
                K key = p.key;
                removeNode(hash(key), key, null, false, false);
                expectedModCount = modCount;
            }
        }
    HashIterator

    构造方法中拿到table

    循环并将index置于table的第一位(第一个有元素的位置)

    将next置为下一位

    再来看看获取下一个节点的方法

    1、将e设为next

    2、将next置为下一位元素

    3、返回e

    使用迭代器一直迭代的话,就应该是从数组前到后

    每次获取数组当前下标链表的下一个元素

    直到元素为null,代表前端链表结束

    数组下标增加,继续遍历下一个链表

    三、总结

    keySet方法并没有存储HashMap的key,而是以迭代器的形式,遍历获取HashMap中的所有key

    对于HashMap的values方法,也是类似的原理

  • 相关阅读:
    ----uni-app之解决底部按钮绝对定位后被软键盘推起的bug----
    ----uni-app之解决app手机键盘消失后页面往上滚的问题----
    mysql tips
    mysql 添加行号
    mysql中的几种join 及 full join问题
    Dcoker中启动mysql,并实现root远程访问
    DockerToolbox在Win7上的安装和设置
    Class.getResourceAsStream 和 ClassLoder.getResourceAsStream 的区别
    tomcat 启动异常 EOFException: Unexpected end of ZLIB input stream
    java异常处理-finally中使用return和throw语句
  • 原文地址:https://www.cnblogs.com/Unicron/p/12741139.html