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方法,也是類似的原理

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM