Java基础面试题

16、常见面试题


1、HashMap和HashTable的区别

区别HashtableHashMap
数据结构数组 + 链表数组 + 链表 + 红黑树(JDK 1.8+)
是否可为nullKey 和 Value 都不能为 null(否则抛出 NPE)Key 和 Value 均可为 null(但 Key 只能有一个 null)
Hash算法直接使用 key.hashCode()二次 Hash 计算((h = key.hashCode()) ^ (h >>> 16)
扩容方式扩容时容量翻倍后 +1(如原容量 10,新容量为 21)直接翻倍(如原容量 16,新容量为 32)
线程安全同步(synchronized 方法),线程安全但性能低非线程安全,性能高,多线程用 ConcurrentHashMap 替代

总结

  • 开发建议:不建议使用 Hashtable,多线程场景优先使用 ConcurrentHashMap

  • 功能差异HashMap 在哈希算法、数据结构、扩容机制上更优化,支持 null 键值。


2、HashSet和HashMap的区别

  1. 接口与用途

    • HashSet 实现 Set 接口,存储唯一元素。

    • HashMap 实现 Map 接口,存储键值对。

  2. 内部实现

    • HashSet 内部封装了一个 HashMap,元素作为键,值统一为 PRESENT(一个静态 Object 对象)。

    • HashMap 直接管理键值对,通过哈希函数计算键的存储位置。

  3. 方法差异

    • HashSet 的 add() 方法调用 HashMap 的 put() 方法,插入元素作为键。 tips: hashCode和equals结果一致的才算相同元素。

    • HashMap 通过 put() 存储键值对,需处理哈希冲突和可能的键覆盖。


3、HashMap的实现原理

  1. 数据结构 JDK 1.8 后采用 数组 + 链表 + 红黑树

    • 默认数组长度 16,负载因子 0.75。

    • 链表长度 >8 时转为红黑树(树化),红黑树节点数 <6 时退化为链表。

  2. 哈希冲突解决

    • 通过 hashCode() 计算键的哈希值,再对数组长度取模确定索引。

    • 哈希冲突时,冲突的键值对以链表或红黑树形式存储。

    • 获取时,直接找到hash值对应的索引,在判断是否有重复的key值,然后输出。

  3. Hash 计算优化 JDK 1.8 的哈希算法:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16),减少碰撞概率。


4、HashMap在JDK1.7和JDK1.8中有什么区别

区别点JDK 1.7JDK 1.8
数据结构数组 + 链表数组 + 链表 + 红黑树
插入方式头插法(可能产生循环链表)尾插法(避免循环链表)
哈希算法多次扰动(4次位运算+5次异或)1次位运算 + 1次异或
扩容机制重新计算所有节点的位置红黑树拆分成的树的节点数<=临界值(6个)则退化为链表
  • 在1.8后:当链表长度大于阈值(默认为8)时并且数组长度达到64时,将链表转化为红黑树


5、HashMap的put方法的具体流程

  1. 初始化检查

    • 若数组 table 为空或为 null,调用 resize() 方法进行初始化(默认容量 16)。

  2. 计算哈希值与索引

    • 根据键 keyhashCode() 计算哈希值,并通过扰动函数优化(如 (h = key.hashCode()) ^ (h >>> 16))。

    • 计算数组索引:索引 = (数组长度 - 1) & hash

  3. 处理空桶

    • table[i] 为空,直接创建新节点并插入。

  4. 处理非空桶(哈希冲突)

    • Key 相同:若 table[i] 的首个节点与当前 key 相同(equals() 为 true),直接覆盖其值。

    • 红黑树插入:若 table[i] 是红黑树节点(TreeNode),调用红黑树的插入方法。

    • 链表插入

      • 遍历链表,若发现 key 已存在则覆盖值。

      • 若未找到相同 key,则在链表尾部插入新节点。

      • 插入后检查链表长度:若链表长度 ≥8 且数组长度 ≥64,将链表转为红黑树。

  5. 扩容检查

    • 插入成功后,若键值对总数 size 超过阈值(容量 × 负载因子,默认 0.75),调用 resize() 扩容。


6、HashMap的扩容机制

  1. 触发条件

    • 元素数量 > 当前容量 × 负载因子(默认 0.75)。

    • 初始化时未指定容量,首次插入数据时触发初始化扩容(默认到 16)。

  2. 扩容步骤

    • 新容量 = 旧容量 × 2,新阈值 = 新容量 × 负载因子。

    • 遍历旧数组,重新分配每个节点到新数组:

      • 单节点:直接按新索引(hash & (newCap-1))插入。

      • 链表或树:根据 hash & oldCap 判断节点应留在原位置还是移动到 原位置 + oldCap

  3. JDK 1.8 优化

    • 无需重新计算哈希值,利用高位判断新位置。

    • 链表拆分时保持原顺序,避免并发环境下死链问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值