本篇涵盖

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

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

一、方法作用

概括一下

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

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

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

二、原理分析

1、HashMap源码分析

keySet方法查看keySet是否为null

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

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

  1. /**
  2. * {@inheritDoc}
  3. *
  4. * @implSpec
  5. * This implementation returns a set that subclasses {@link AbstractSet}.
  6. * The subclass's iterator method returns a "wrapper object" over this
  7. * map's <tt>entrySet()</tt> iterator. The <tt>size</tt> method
  8. * delegates to this map's <tt>size</tt> method and the
  9. * <tt>contains</tt> method delegates to this map's
  10. * <tt>containsKey</tt> method.
  11. *
  12. * <p>The set is created the first time this method is called,
  13. * and returned in response to all subsequent calls. No synchronization
  14. * is performed, so there is a slight chance that multiple calls to this
  15. * method will not all return the same set.
  16. */
  17. public Set<K> keySet() {
  18. Set<K> ks = keySet;
  19. if (ks == null) {
  20. ks = new AbstractSet<K>() {
  21. public Iterator<K> iterator() {
  22. return new Iterator<K>() {
  23. private Iterator<Entry<K,V>> i = entrySet().iterator();
  24.  
  25. public boolean hasNext() {
  26. return i.hasNext();
  27. }
  28.  
  29. public K next() {
  30. return i.next().getKey();
  31. }
  32.  
  33. public void remove() {
  34. i.remove();
  35. }
  36. };
  37. }
  38.  
  39. public int size() {
  40. return AbstractMap.this.size();
  41. }
  42.  
  43. public boolean isEmpty() {
  44. return AbstractMap.this.isEmpty();
  45. }
  46.  
  47. public void clear() {
  48. AbstractMap.this.clear();
  49. }
  50.  
  51. public boolean contains(Object k) {
  52. return AbstractMap.this.containsKey(k);
  53. }
  54. };
  55. keySet = ks;
  56. }
  57. return ks;
  58. }

keySet构造函数

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

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

再来看KeySet这个类

  1. final class KeySet extends AbstractSet<K> {
  2. public final int size() { return size; }
  3. public final void clear() { HashMap.this.clear(); }
  4. public final Iterator<K> iterator() { return new KeyIterator(); }
  5. public final boolean contains(Object o) { return containsKey(o); }
  6. public final boolean remove(Object key) {
  7. return removeNode(hash(key), key, null, false, true) != null;
  8. }
  9. public final Spliterator<K> spliterator() {
  10. return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
  11. }
  12. public final void forEach(Consumer<? super K> action) {
  13. Node<K,V>[] tab;
  14. if (action == null)
  15. throw new NullPointerException();
  16. if (size > 0 && (tab = table) != null) {
  17. int mc = modCount;
  18. for (int i = 0; i < tab.length; ++i) {
  19. for (Node<K,V> e = tab[i]; e != null; e = e.next)
  20. action.accept(e.key);
  21. }
  22. if (modCount != mc)
  23. throw new ConcurrentModificationException();
  24. }
  25. }
  26. }

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迭代器

进入父类

  1. abstract class HashIterator {
  2. Node<K,V> next; // next entry to return
  3. Node<K,V> current; // current entry
  4. int expectedModCount; // for fast-fail
  5. int index; // current slot
  6.  
  7. HashIterator() {
  8. expectedModCount = modCount;
  9. Node<K,V>[] t = table;
  10. current = next = null;
  11. index = 0;
  12. if (t != null && size > 0) { // advance to first entry
  13. do {} while (index < t.length && (next = t[index++]) == null);
  14. }
  15. }
  16.  
  17. public final boolean hasNext() {
  18. return next != null;
  19. }
  20.  
  21. final Node<K,V> nextNode() {
  22. Node<K,V>[] t;
  23. Node<K,V> e = next;
  24. if (modCount != expectedModCount)
  25. throw new ConcurrentModificationException();
  26. if (e == null)
  27. throw new NoSuchElementException();
  28. if ((next = (current = e).next) == null && (t = table) != null) {
  29. do {} while (index < t.length && (next = t[index++]) == null);
  30. }
  31. return e;
  32. }
  33.  
  34. public final void remove() {
  35. Node<K,V> p = current;
  36. if (p == null)
  37. throw new IllegalStateException();
  38. if (modCount != expectedModCount)
  39. throw new ConcurrentModificationException();
  40. current = null;
  41. K key = p.key;
  42. removeNode(hash(key), key, null, false, false);
  43. expectedModCount = modCount;
  44. }
  45. }

HashIterator

构造方法中拿到table

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

将next置为下一位

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

1、将e设为next

2、将next置为下一位元素

3、返回e

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

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

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

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

三、总结

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

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

HashMap之KeySet分析的更多相关文章

  1. 【JAVA集合】HashMap源码分析(转载)

    原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储 ...

  2. HashMap源码分析和应用实例的介绍

    1.HashMap介绍 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.S ...

  3. HashMap源码分析(史上最详细的源码分析)

    HashMap简介 HashMap是开发中使用频率最高的用于映射(键值对 key value)处理的数据结构,我们经常把hashMap数据结构叫做散列链表: ObjectI entry<Key, ...

  4. Java中HashMap源码分析

    一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtab ...

  5. 基础进阶(一)之HashMap实现原理分析

    HashMap实现原理分析 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二 ...

  6. JDK1.8 HashMap源码分析

      一.HashMap概述 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时 ...

  7. 【Java】HashMap源码分析——常用方法详解

    上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...

  8. 【Java】HashMap源码分析——基本概念

    在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...

  9. jdk1.8 HashMap的keySet方法详解

    我在看HashMap源码的时候有一个问题让我产生了兴趣,那就是HashMap的keySet方法,没有调用HashMap的有关数据的任何方法就能获取到map的所有的键,他是怎么做到的,然后我就通过模拟k ...

随机推荐

  1. 太赞了!阿里几位工程师重写了 《Java 并发编程》

    事情是这样的,前些日子和得知一个读者在准备阿里的面试,我蛮有兴趣的跟他聊了起来,随着话题越来越深入,我发现这位读者有意思,他和几位阿里的工程师之前编写了一本 concurrent.redspider. ...

  2. 使命召唤:战区国际服ID注册与登录

    命召唤:战区 国际服ID注册与登录 1.下面官网网页注册国际服账号.2登录游戏.就这么简单.(前提是网咖.电竞宾馆.已经提供好游戏)  !!!注意 如果是网吧网咖电竞宾馆,用其给你提供的游戏图标进入游 ...

  3. 【笔记3-31】Python语言基础-序列sequence

    序列sequence 可变序列 列表 list 字典 不可变序列 字符串 str 元祖 tuple 通过索引修改列表 del 删除元素 del my_list[2] 切片赋值只能是序列 .insert ...

  4. python win32com

    要使用Python控制MS Word,您需要先安裝win32com套件,這個套件可以到 http://sourceforge.net/projects/pywin32/ 找到.本文假設您已經正確安裝w ...

  5. vue - Babel之babel-polyfill、babel-runtime、transform-runtime

    引言 babel默认只转换新的 JavaScript 语法,比如箭头函数.扩展运算(spread). 不转换新的 API,例如Iterator.Generator.Set.Maps.Proxy.Ref ...

  6. 记录一次MAC连接投影闪屏的问题。

    遇到的问题:MAC笔记本连接投影出现闪屏怎么办? 解决办法:尝试过很多种办法,后面发现这个闪屏原因是投影机的refresh rate 默认不支持这么高的.调整到30hz左右即可. 步骤:使用HDMI转 ...

  7. coding++:error Could not read JSON: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object

    Spring源码中是使用容器中的ObjectMapper对象进行序列化和反序列化. 当我们将自定义的ObjectMapper对象放入IOC容器中后,会自动覆盖SpringBoot自动装载的Object ...

  8. linux 中的页缓存和文件 IO

    本文所述是针对 linux 引入了虚拟内存管理机制以后所涉及的知识点.linux 中页缓存的本质就是对于磁盘中的部分数据在内存中保留一定的副本,使得应用程序能够快速的读取到磁盘中相应的数据,并实现不同 ...

  9. 403 Invalid CORS request 跨域问题解决

    这里使用springMVC自带的CORS解决跨域问题 什么是跨域问题 1.请求地址与当前地址不相同 2.端口号不相同 技术有限端口号不同还未发现 3.二级域名不相同 出现这种问题如何解决有很多种方法, ...

  10. D - D 分糖果HDU - 1059(完全背包+二进制优化)

    有两个小朋友想要平分一大堆糖果,但他们不知道如何平分需要你的帮助,由于没有spj我们只需回答能否平分即可. 糖果大小有6种分别是1.2.3.4.5.6,每种若干颗,现在需要知道能不能将这些糖果分成等额 ...