# HashMap 和 ConcurrentHashMap 面试问题 > 原文: [https://howtodoinjava.com/interview-questions/hashmap-concurrenthashmap-interview-questions/](https://howtodoinjava.com/interview-questions/hashmap-concurrenthashmap-interview-questions/) 在“ [**中,HashMap 如何在 Java**](//howtodoinjava.com/java/collections/how-hashmap-works-in-java/ "How hashmap works in java") 中工作”,我解释了`HashMap`或`ConcurrentHashMap`类的内部结构以及它们如何适合整个概念。 但是,当面试官向您询问有关`HashMap`相关概念时,他不仅会停留在核心概念上。 讨论通常会朝多个方向进行,以了解您是否真正了解该概念。 在这篇文章中,我将尝试在 **HashMap** 和 **ConcurrentHashMap** 上涵盖一些相关的 [**面试问题**](//howtodoinjava.com/java-interview-questions/ "interview questions") 。 ## HashMap 和 ConcurrentHashMap 面试问题 1\. [您将如何为 HashMap 设计一个好的密钥?](#1) 2\. [HashMap 和 ConcurrentHashMap 之间的区别?](#2) 3\. [HashMap 和 Collections.synchronizedMap(HashMap)之间的区别吗?](#3) 4\. [ConcurrentHashMap 和 Collections.synchronizedMap(HashMap)之间的区别?](#4) 5\. [HashMap 和 HashTable 之间的区别?](#5) 6\. [HashTable 和 Collections.synchronized(HashMap)之间的区别?](#6) 7\. [密钥的随机/固定 hashCode()值的影响?](#7) 8\. [在多线程应用的非同步代码中使用 HashMap 吗?](#8) ## 1.如何为 HashMap 设计一个好的密钥 设计一个好的密钥的最基本的需求是“我们应该能够从地图中检索到价值对象而不会失败”,对吗? 否则,无论您如何构建精美的数据结构,它都将毫无用处。 要确定我们已经创建了一个好的密钥,我们必须知道“ [**HashMap 如何工作?**](https://howtodoinjava.com/java/collections/how-hashmap-works-in-java/ "How hashmap works in java") 。 我将介绍散列表的工作原理,让您从链接的文章中阅读内容,但总而言之,它是基于散列原理的。 键的哈希码主要与 equals()方法结合使用,用于将键放入地图,然后从地图中搜索回来。 因此,如果在将键值对放入映射后,键对象的哈希码发生变化,则几乎不可能从映射取回值对象。 这是内存泄漏的情况。 为了避免这种情况,映射**键应该是不可变的**。 这些是 [**创建类**](https://howtodoinjava.com/java/basics/how-to-make-a-java-class-immutable/ "How to make a java class immutable") 不变的东西。 这就是为什么不可变类(例如 String,Integer 或其他包装器类)是好的关键对象候选对象的主要原因。 但是请记住,建议**不可变,而不是强制性**。 如果要将可变对象作为哈希图中的键,则必须确保键对象的**状态更改不会更改对象**的哈希码。 这可以通过重写 hashCode()方法来完成。 同样,键类必须遵守 [**hashCode()和 equals()方法协定**](https://howtodoinjava.com/java/basics/java-hashcode-equals-methods/ "Working with hashCode and equals methods in java") ,以避免在运行时出现不良行为。 在链接的文章中阅读有关此合同的更多信息。 更详细的信息可在 [**在此处**](https://howtodoinjava.com/java/collections/design-good-key-for-hashmap/ "How to design a good key for HashMap") 中找到。 ## 2\. HashMap 和 ConcurrentHashMap 之间的区别 为了更好地可视化 ConcurrentHashMap,请将其视为一组 HashMap。 要从哈希图中获取和放置键值对,您必须计算哈希码并在 Collection.Entry 数组中查找正确的存储桶位置。 其余的,您已阅读了以前有关散列表如何工作的相关文章。 在 currentHashMap 中,**的不同之处在于内部结构来存储这些键值对**。 ConcurrentHashMap 具有段的附加概念。 当您想到一个段等于一个 HashMap 时,[概念上]会更容易理解。 初始化时,并发 HashMap 被划分为段数[默认为 16]。 ConcurrentHashMap 允许相似数量(16)的线程同时访问这些段,以便在高并发期间每个线程都在特定的段上工作。 这样,如果您的键值对存储在第 10 段中; 代码不需要另外阻塞其他 15 个段。 这种结构提供了很高的并发性。 ![ConcurrentHashMap Internal Structure](img/d10fabdd2b20d60555a8221a36905eeb.png) ConcurrentHashMap Internal Structure 换句话说, **ConcurrentHashMap 使用多个锁,每个锁控制映射**的一个段。 在特定段中设置数据时,将获得该段的锁。 因此,基本上**更新操作是同步的**。 **获取数据时,将使用易失性读取**,而不会进行任何同步。 如果易失性读取导致未命中,则将获得该段的锁定,并在同步块中再次搜索条目。 ## 3\. HashMap 和 Collections.synchronizedMap(HashMap)之间的区别 这个问题很简单,对! HashMap 是不同步的,并且 Collections.synchronizedMap()返回 HashMap 的包装实例,该实例具有同步的所有 get,put 方法。 本质上, **Collections.synchronizedMap()返回内部创建的内部类“ SynchronizedMap”** 的引用,该类包含作为参数传递的输入 HashMap 的键值对。 内部类的此实例与原始参数 HashMap 实例无关,并且是完全独立的。 ## 4\. ConcurrentHashMap 和 Collections.synchronizedMap(HashMap)之间的区别 这一点要难一些。 两者都是 HashMap 的同步版本,其核心功能和内部结构有所不同。 如上所述,ConcurrentHashMap 由内部段组成,从概念上讲,这些内部段可以视为独立的 HashMap。 所有这样的段可以在高并发执行中被单独的线程锁定。 这样,**多个线程可以从 ConcurrentHashMap 获取/放置键值对,而不会互相阻塞/等待**。 在 Collections.synchronizedMap()中,我们获得了 HashMap 的同步版本,并且**以阻塞方式**进行访问。 这意味着如果多个线程尝试同时访问 syncedMap,将允许它们一次以同步方式获取/放置键值对。 ## 5\. HashMap 和 HashTable 之间的区别 这也是一个很简单的问题。 主要区别在于 **HashTable 是同步的,而 HashMap 不是**。 如果由于其他原因而被告知,请告诉他们, **HashTable 是旧类**(JDK 1.0 的一部分),该类稍后通过实现 Map 接口而被提升为集合框架。 它仍然具有一些**附加功能,例如 HashMap 缺少的 Enumerator** 。 另一个较小的原因可能是: **HashMap 支持空键**(映射到零桶),HashTable 不支持空键,并且在这种尝试时抛出 NullPointerException。 ## 6\. HashTable 和 Collections.synchronized(HashMap)之间的区别 到目前为止,您必须已经了解了它们之间相似性的核心思想。 两者都是集合的同步版本。 两者都在类内部具有同步方法。 两者本质上都是阻塞的,即在将实例中的任何内容放进去之前,多个线程将需要等待获取实例上的锁。 那么区别是什么呢。 好吧,由于上述原因,**没有重大差异**。 两个集合的性能也相同。 唯一将它们分开的是事实 **HashTable 是提升为收集框架的旧版**类。 它具有自己的额外功能,例如枚举器。 ## 7.密钥的随机/固定 hashcode()值的影响 两种情况(密钥的固定哈希码或密钥的随机哈希码)的影响将产生相同的结果,即“ **意外行为**”。 HashMap 中哈希码最基本的需求是确定存储桶的位置,以将键值对放置在哪里,以及必须从哪里检索它。 如果键对象的哈希码每次都更改,则键值对的确切位置每次都将计算为不同。 这样,存储在 HashMap 中的一个对象将永远丢失,并且将其从地图取回的可能性极小。 出于同样的原因,建议密钥是不可变的,以便每次在同一密钥对象上请求时,它们都返回唯一且相同的哈希码。 ## 8.在多线程应用中的非同步代码中使用 HashMap 在正常情况下,它**可使哈希图处于不一致状态**,在该状态下添加和检索的键值对可能不同。 除此之外,还会出现其他令人惊讶的行为,例如 NullPointerException。 在最坏的情况下,**会导致无限循环**。 是。 你答对了。 它可能导致无限循环。 你问什么,如何? 好吧,这就是原因。 当 HashMap 达到其大小上限时,它具有重新哈希的概念。 重新哈希处理是创建新的内存区域,并在新的内存中复制所有已经存在的键值对的过程。 可以说,线程 A 尝试将键值对放入映射中,然后开始重新哈希。 同时,线程 B 来了,并开始使用 put 操作来操作存储桶。 在进行重新哈希处理时,有机会生成循环依赖关系,其中链表中的任何元素(在任何存储桶中)都可以指向同一存储桶中的任何先前节点。 这将导致无限循环,因为重新哈希处理的代码包含一个“ while(true){//获取下一个节点; }”块,并且在循环依赖项下它将无限运行。 要仔细观察,请查看用于重新哈希处理的传输方法的艺术源代码: ```java public Object get(Object key) { Object k = maskNull(key); int hash = hash(k); int i = indexFor(hash, table.length); Entry e = table[i]; //While true is always a bad practice and cause infinite loops while (true) { if (e == null) return e; if (e.hash == hash & eq(k, e.key)) return e.value; e = e.next; } } ``` 以后我会写一篇更详细的文章。 希望我能够为**哈希图面试问题**和 **ConcurrentHashMap 面试问题**投入更多知识。 如果您认为本文有帮助,请考虑与您的朋友分享。 学习愉快! 参考文献: [ConcurrentHashMap Java 文档](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/ConcurrentHashMap.html) [ConcurrentHashMap Java 文档](https://docs.oracle.com/javase/10/docs/api/java/util/HashMap.html)