本篇涵盖

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

最新文章

  1. Sublime Text3 以及 SublimeREPL使用Virtualenv执行python
  2. PDF 补丁丁 0.4.1.820 测试版发布:统一PDF的页面尺寸
  3. Qt之QPropertyAnimation
  4. 使用文件监控对象FileSystemWatcher实现数据同步
  5. HP quality center 9.0 邮件设置
  6. python之lambda、filter、map、reduce的用法说明
  7. 对yield 的理解
  8. Ajax中与服务器的通信【发送请求与处理响应】
  9. leetcode 1——两数之和
  10. python 递归实现汉诺塔算法
  11. Leetcode刷题第004天
  12. 【Python】【面向对象】
  13. shiro学习笔记(四) ini配置以及加解密
  14. Unity3D实践系列05,为GameObject添加额外属性
  15. PHP安装sqlsrv和memcache扩展步骤
  16. Proxy 代理模式 动态代理 CGLIB
  17. C语言位域精解(转)
  18. jQuery子页面获取父页面元素
  19. ASP.NET错误处理的方式(二)
  20. A. The Meaningless Game(数学)

热门文章

  1. 前端CSS学习笔记
  2. 关于Web2.0
  3. NLP interview
  4. 使用Promethus+Grafana监控Mongodb
  5. 一文上手Tensorflow2.0(四)
  6. python实现十大经典排序算法
  7. 树莓派 zeroWH 使用笔记
  8. 基于 HTML5 WebGL 的楼宇智能化集成系统(一)
  9. 1642: 【USACO】Payback(还债)
  10. flink 一分钟入门篇