本篇涵蓋
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; }
代碼注釋中提到,創建的對象是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(); } } }
里面包含了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; } }
構造方法中拿到table
循環並將index置於table的第一位(第一個有元素的位置)
將next置為下一位
再來看看獲取下一個節點的方法
1、將e設為next
2、將next置為下一位元素
3、返回e
使用迭代器一直迭代的話,就應該是從數組前到后
每次獲取數組當前下標鏈表的下一個元素
直到元素為null,代表前端鏈表結束
數組下標增加,繼續遍歷下一個鏈表
三、總結
keySet方法並沒有存儲HashMap的key,而是以迭代器的形式,遍歷獲取HashMap中的所有key
對於HashMap的values方法,也是類似的原理