热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Java源码解析HashMap的keySet()方法

今天小编就为大家分享一篇关于Java源码解析HashMap的keySet()方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

HashMap的keySet()方法比较简单,作用是获取HashMap中的key的集合。虽然这个方法十分简单,似乎没有什么可供分析的,但真正看了源码,发现自己还是有很多不懂的地方。下面是keySet的代码。

  public Set keySet() {
    Set ks = keySet;
    if (ks == null) {
      ks = new KeySet();
      keySet = ks;
    }
    return ks;
  }

从代码中了解到,第一次调用keySet方法时,keySet属性是null,然后进行了初始化,再将keySet属性返回。也就是说,HashMap里并不会随着put和remove的进行也维护一个keySet集合,而是在第一次调用keySet方法时,才给keySet属性初始化。

按照自己以往的理解,以为keySet返回的是一个集合,集合里面保存了HashMap的所有的Key。因为有了中先入为主的印象,所以读源码时,才感觉源码很奇怪。从源码中可以看到,初始化时,只是创建了一个KeySet类的对象,并没有把HashMap的key都加入进来,方法就返回了。除了自己以往的理解外,还有一个现象,让我坚信这时HashMap的key已经加入到keySet了,那就是在调试代码过程中IDE给出的调试信息。如下图。从图中可以看出,创建完成KeySet()后,调试信息就已经可以显示出,ks中有2个元素了。这个信息更加坚定了自己之前的理解。

那么,HashMap的key是什么时候加入到keySet集合中的呢?顺着这个思路,我进行了一步一步的分析。自己看了KeySet类的构造函数,发现只有默认构造函数。那么我想,如果没有在KeySet构造函数里把HashMap的key加入进来,那么就有可能是在KeySet的父类的构造函数中加入进来的。然后,自己找遍了KeySet类的父类的构造函数,发现都是空实现,并没有任何加入HashMap的key的操作。这到底是怎么回事呢?

其实HashMap的key并没有加入到keySet集合中,而是在遍历的时候,使用迭代器对key进行的遍历。这是结论。下面我们看一下原因和过程。

首先看一下KeySet类的代码,如下图。可以看到,KeySet类中的迭代器函数,返回的是一个KeyIterator类的对象。它的next方法返回的是HashIterator的nextNode的key。也就是说,当使用迭代器遍历set内的元素时,KeySet类的迭代器,会保证能够依次获取到HashMap的节点的key值,这就是我们遍历keySet的过程的实质。

  final class KeySet extends AbstractSet {
    public final int size()         { return size; }
    public final void clear()        { HashMap.this.clear(); }
    public final Iterator 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 spliterator() {
      return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    public final void forEach(Consumer<&#63; super K> action) {
      Node[] tab;
      if (action == null)
        throw new NullPointerException();
      if (size > 0 && (tab = table) != null) {
        int mc = modCount;
        for (int i = 0; i  e = tab[i]; e != null; e = e.next)
            action.accept(e.key);
        }
        if (modCount != mc)
          throw new ConcurrentModificationException();
      }
    }
  }
  final class KeyIterator extends HashIterator
    implements Iterator {
    public final K next() { return nextNode().key; }
  }
  abstract class HashIterator {
    Node next;    // next entry to return
    Node current;   // current entry
    int expectedModCount; // for fast-fail
    int index;       // current slot
    HashIterator() {
      expectedModCount = modCount;
      Node[] t = table;
      current = next = null;
      index = 0;
      if (t != null && size > 0) { // advance to first entry
        do {} while (index  nextNode() {
      Node[] t;
      Node 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  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;
    }
  }

那么,这里我们可以思考这么一个问题。通过HashMap的keySet获取到keySet后,难道只能用迭代器遍历吗?keySet方法不把HashMap的key都加入到set中,那么调用者使用for(int i = 0; i

这里还有个问题需要解释,就是在调试代码时,既然key没有加入到set中,那么IDE如何显示出set中有2个元素这样的信息的?原来,IDE显示对象信息时,会调用对象的toString方法。而集合的toString方法就是显示出集合中的元素个数。

这里再思考一步,如果我们在集合的toString方法加上断点,那么IDE显示对象信息时,会不先停下来?答案是看情况。记得早些年间使用eclipse调试代码时,在toString方法加上断点后,显示对象信息时确实会停下来。然而我现在使用的是IDE是idea,idea在这一点上做了优化。如果是IDE显示对象信息调用的toString方法,那么toString方法的断点会被跳过,即不生效,但会给出一条提示信息,如下图。如果程序员主动调用对象的toString方法,那么,toString方法的断点会生效,可以正常断点调试。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。如果你想了解更多相关内容请查看下面相关链接


推荐阅读
  • 本文深入探讨了 JavaScript 中的匿名函数,包括其定义、如何定义以及在实际开发中的多种应用场景。匿名函数,即无名称的函数,是 JavaScript 编程中的重要组成部分,对于提升代码效率和可维护性具有重要作用。 ... [详细]
  • 本文详细介绍了Python中的流程控制与条件判断技术,包括数据导入、数据变换、统计描述、假设检验、可视化以及自定义函数的创建等方面的内容。 ... [详细]
  • Processing入门教程(二十六) sin()and cos()
    Processing入门教程(二十六)sin()andcos(),Go语言社区,Golang程序员人脉社 ... [详细]
  • 解决SVN E200007错误的方法
    本文详细介绍了在使用SVN提交代码时遇到E200007错误的原因及解决方案,特别是针对服务器地址变更后的问题。 ... [详细]
  • 深入解析 Zend Guard 4 功能与配置
    本文旨在通过图文并茂的方式详细介绍 Zend Guard 4 的核心功能及其配置方法,特别适合英文阅读能力有限的技术人员。文中不仅提供了详细的步骤说明,还附带了实际操作中的注意事项,帮助读者更好地理解和应用这一工具。 ... [详细]
  • 【Java数据结构和算法】008栈
    目录0、警醒自己一、栈的应用场景和介绍1、栈的应用场景一个实际的场景:我的思考:2、栈的介绍入栈演示图:出栈演示图 ... [详细]
  • 重新审视Kubernetes的必要性
    近期,我注意到不少人误以为我是Kubernetes的推广者。实际上,我只是分享了一些适合用Kubernetes解决的案例。本文旨在探讨Kubernetes的适用场景,并提供一些替代方案。 ... [详细]
  • 本文深入探讨了分布式文件系统的核心概念及其在现代数据存储解决方案中的应用,特别是针对大规模数据处理的需求。文章不仅介绍了多种流行的分布式文件系统和NoSQL数据库,还提供了选择合适系统的指导原则。 ... [详细]
  • 1<table>2<tr>3<th>ID<th>4 ... [详细]
  • 面对日益竞争激烈的就业市场,合理的职业规划对于在校大学生尤为重要。本文旨在探讨如何通过有效的自我认知、技能提升及目标设定,帮助计算机专业的学生构建清晰的职业路径,以增强就业竞争力。 ... [详细]
  • Golang与微服务架构:构建高效微服务
    本文探讨了Golang在微服务架构中的应用,包括Golang的基本概念、微服务开发的优势、常用开发工具以及具体实践案例。 ... [详细]
  • 本文介绍了如何使用Workman框架构建一个功能全面的即时通讯系统,该系统不仅支持一对一聊天、群组聊天,还集成了视频会议和实时音视频通话功能,同时提供了红包发送等附加功能。 ... [详细]
  • 深入解析链表成环问题:剑指Offer第22天的新视角
    本文将详细介绍链表成环问题的多种解法,包括哈希表法、JSON.stringify特殊解法及双指针法,并提供详尽的代码示例。阅读本文,你不仅能够掌握这一经典算法问题的核心技巧,还能了解到更多编程思维的拓展。 ... [详细]
  • 本文旨在介绍一系列提升工作效率的浏览器插件和实用小工具,帮助用户在日常工作中更加便捷高效。内容由原作者授权发布。 ... [详细]
  • 深入探讨LeetCode上的一道经典算法题——判断一个整数是否为4的幂,提供高效解决方案。 ... [详细]
author-avatar
水皱皱_446
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有