Hash Map相关面试题

本文深入解析了HashMap的基本特性,包括其线程不安全性、基于数组和链表的底层实现、高效的查询速度以及三种遍历方式。此外,还详细介绍了HashMap的扩容机制、数组结构,并对比了HashMap与HashTable的主要区别,如线程安全性、同步机制、迭代器类型及性能差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HashMap的基本特性

  1. HashMap是线程不安全的;
  2. HashMap的底层主要是基于数组和链表实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储位置的
  3. HashMap的遍历方式
    1. HashMap<Integer,Integer> hashMap = new HashMap<>();
      hashMap.put(1,2);
      hashMap.put(2,4);
      //第一种:普通使用,二次取值(性能差)
      for (Integer key:hashMap.keySet()){
          System.out.println(key);
      }
      //第二种:性能比第一种好,一次取值
      Iterator<Map.Entry<Integer, Integer>> iterator = hashMap.entrySet().iterator();
      while (iterator.hasNext()){
          Map.Entry<Integer, Integer> next = iterator.next();
          System.out.println(next.getKey()+next.getValue());
      }
      //第三种:推荐使用,尤其是容量大的
      for(Map.Entry<Integer,Integer> entry:hashMap.entrySet()){
          System.out.println(entry.getKey()+entry.getValue());
      }
      //第四种:
      for (Integer value:hashMap.values()){
          System.out.println(value);
      }
      //第五种:java8使用foreach进行遍历
      hashMap.forEach((k,v) -> System.out.println(k+v));
  4. 扩容机制:
    1. HashMap默认的负载因子为0.75f,默认初始大小为16;
    2. 数组大小*负载因子=要调整大小的值(临界值)
    3. 从源代码来看HashMap,在添加时执行扩容方法,执行扩容方法时必须两个条件同时相等,(链表的总条数>=要调整大小的值)和当前要添加的值的索引位置不为null(也就是说当前要添加值得索引位置里面有值),才会执行扩容方法,扩容的大小为原来的数组长度乘以2(原来的数组长度的二倍)。扩容之后所有元素还会根据key来重新计算hash值进行重新添加元素,重新执行顺序为下标从0开始,里面的链表从外层开始。 
  5. 数组结构:
    1. Hash Map底层就是一个数组,数组中的每一项又是一个链表,新建一个HashMap的时候,就会初始化一个数组

 

 

HashMap和HashTable的区别?

HashMap和HashTable都实现了Map接口,主要区别有线程安全性,同步(synchronize),以及速度。

           1.HashMap几乎等价于HashTable,除了Hash Map是非synchronize的,并可以接受null(Hash Map可以接受为null的键值和key值,而HashTable则不行。

             2.HashMap是非synchronized,而HashTable是synchronized,这意味着HashTable是线程安全的,多个线程可以共享一个HashTable;而如果没有正确的同步的话,多个线程是不能共享HashMap的,Java5提供了ConcurrentHashMap,他是HashTable的替代,比HashTable的扩展性更好。

             3.另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而HashTable的enumerator迭代器不是fail-fast的,所以当有其他线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但是这并不是一个一定发生的行为,要看JVM,这条同样也是Enumeration和Iterator的区别。

             4.由于HashTable是线程安全也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过HashTable

             5.HashMap不能保证随着时间的推移Map中的元素次序是不变的。

<think>嗯,用户现在问的是关于Map集合的面试题。之前他们问过Spring注解,现在转向Java集合,可能是在准备Java相关的面试,需要复习常见的问题。首先,我需要回忆常见的Map相关面试题,比如HashMap的工作原理、线程安全问题、ConcurrentHashMap的实现,还有红黑树和链表的结构转换等。 用户可能希望得到全面的问题列表和详细的解答,但需要结构清晰,分点说明。可能他们需要对比不同Map实现类的区别,比如HashMapHashtable、ConcurrentHashMap。还要注意源码层面的问题,比如hash计算、扩容机制。 另外,考虑到之前的回答用了Markdown,并且分模块,这次也需要保持类似的格式,让内容易读。可能需要包括基本概念、常见问题、源码解析、使用场景和注意事项等部分。 还要注意用户可能的盲点,比如JDK8之后HashMap引入红黑树的优化,ConcurrentHashMap的分段锁和CAS操作。此外,线程安全的问题和如何解决也是常见考点。 需要确保回答准确,比如HashMap的负载因子默认值,扩容倍数,以及为什么长度是2的幂次。同时,解答要尽量详细,但不过于冗长,适合面试准备时快速回顾。 可能还需要加入一些代码示例,比如如何正确使用HashMap,或者ConcurrentHashMap线程安全示例。另外,强调实践中的最佳做法,比如初始化容量,选择合适的Map实现类。 最后,检查是否有遗漏的重要问题,比如哈希冲突解决、遍历方式、fail-fast机制等。确保覆盖所有常见考点,帮助用户全面准备。</think>以下是关于 Java `Map` 集合的常见面试题及详解,内容覆盖底层原理、高频考点和实际应用场景,帮助系统性准备面试: --- ### **一、基础概念** 1. **Map 的核心特点** - **键值对存储**:通过 `Key` 快速查找 `Value`,`Key` 不可重复,`Value` 可重复。 - **常用实现类**: - `HashMap`(无序,线程不安全) - `LinkedHashMap`(有序,按插入或访问顺序) - `TreeMap`(有序,按 `Key` 自然排序或自定义比较器) - `Hashtable`(线程安全,已逐渐被 `ConcurrentHashMap` 替代) - `ConcurrentHashMap`(高并发场景下的线程安全实现) 2. **HashMapHashtable 的区别** | **特性** | **HashMap** | **Hashtable** | |----------------|-------------------|-------------------| | **线程安全** | 不安全 | 安全(方法用 `synchronized` 修饰) | | **Null 支持** | 允许 `Key` 和 `Value` 为 `null` | `Key` 和 `Value` 均不可为 `null` | | **性能** | 更高(无锁竞争) | 较低(锁粒度大) | | **初始容量** | 16 | 11 | --- ### **二、高频考点(HashMap 底层原理)** #### **1. 数据结构与哈希冲突解决** - **JDK 1.7 及之前**:数组 + 链表(哈希冲突时,新元素插入链表头部)。 - **JDK 1.8 及之后**:数组 + 链表/红黑树(链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转为红黑树)。 ```java // JDK 1.8 的节点定义(链表或红黑树) static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; // 链表结构 } ``` #### **2. 哈希函数设计** - **核心逻辑**:`Key` 的 `hashCode()` 经过扰动计算(减少哈希冲突)。 ```java // JDK 1.8 的扰动函数 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } ``` - **数组长度为什么是 2 的幂次?** 方便通过 `(n - 1) & hash` 快速定位索引,等同于取模运算(`hash % n`),但位运算效率更高。 #### **3. 扩容机制(Resize)** - **触发条件**:当元素数量超过 `容量 × 负载因子`(默认负载因子 `0.75`)。 - **扩容操作**: 1. 创建新数组(大小为原数组的 2 倍)。 2. 重新计算每个元素的哈希位置(利用高位判断,避免全量重新哈希)。 - **链表拆分优化(JDK 1.8)**: 根据 `hash & oldCap` 是否为 0,将链表拆分为低位链表(原索引)和高位链表(原索引 + 原容量)。 --- ### **三、线程安全问题** #### **1. HashMap 为什么线程不安全?** - **场景 1:多线程扩容导致死循环(JDK 1.7)** 链表头插法在并发扩容时可能形成环形链表,导致 `get()` 死循环。 - **场景 2:数据覆盖(JDK 1.8)** 多线程同时执行 `put()` 时,可能覆盖已写入的键值对。 #### **2. ConcurrentHashMap 如何保证线程安全?** - **JDK 1.7**:分段锁(Segment),每个段独立加锁,降低锁粒度。 - **JDK 1.8**: - 取消分段锁,采用 `Node` 数组 + `CAS`(无锁化操作) + `synchronized`(仅锁住链表头或红黑树根节点)。 - **关键代码片段**: ```java final V putVal(K key, V value, boolean onlyIfAbsent) { // 使用 CAS 尝试无锁插入 if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; } // 若存在哈希冲突,则对头节点加 synchronized 锁 else { synchronized (f) { // 插入或更新节点 } } } ``` --- ### **四、进阶问题** #### **1. LinkedHashMap 如何实现有序性?** - 继承 `HashMap`,通过双向链表维护插入顺序或访问顺序(LRU 缓存的基础)。 - **访问顺序模式**:重写 `removeEldestEntry()` 可实现固定大小的 LRU 缓存。 #### **2. TreeMap 的排序规则** - 基于红黑树实现,支持: - **自然排序**:`Key` 实现 `Comparable` 接口。 - **定制排序**:通过 `Comparator` 传入比较器。 #### **3. 如何设计一个线程安全的 Map?** - **方案 1**:使用 `Collections.synchronizedMap(new HashMap<>())`(锁住整个 Map)。 - **方案 2**:使用 `ConcurrentHashMap`(推荐,锁粒度更小)。 - **方案 3**:自定义锁分段(参考 JDK 1.7 的 `ConcurrentHashMap` 实现)。 --- ### **五、实战技巧** 1. **初始化容量优化** - 预估数据量,避免频繁扩容(如数据量 1000,负载因子 0.75,则初始容量设为 `(1000 / 0.75) + 1 ≈ 1334`)。 2. **Key 的选择** - 使用不可变对象(如 `String`、`Integer`),避免修改 `Key` 导致哈希值变化。 3. **遍历方式** - 使用 `entrySet()` 遍历(直接获取键值对,效率高于 `keySet()` + `get()`)。 --- ### **六、常见面试题示例** 1. **HashMap 的负载因子为什么默认是 0.75?** - 权衡时间与空间:负载因子过小(如 0.5)会导致频繁扩容,过大(如 1.0)会增加哈希冲突概率。 2. **ConcurrentHashMap 的 size() 方法如何实现?** - JDK 1.8 中基于 `baseCount` 和 `CounterCell[]` 的分段统计,减少并发竞争。 3. **如何让 HashMap 支持插入顺序遍历?** - 改用 `LinkedHashMap`,并在构造方法中设置 `accessOrder=false`(默认按插入顺序)。 --- 通过理解这些核心问题,可以系统性地掌握 `Map` 的设计思想和应用场景,应对面试中的深度追问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值