ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【秋招必看】Java 集合面试热题(一)

【秋招必看】Java 集合面试热题(一) 目录1.说说 Java 中 HashMap 的原理2.Java 中的 List 接口有哪些实现类3.Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别4.为什么 JDK 1.8 对 HashMap 进行了红黑树的改动5.JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动6.Java 中有哪些集合类请简单介绍。7.为什么 Java 中 HashMap 的默认负载因子是 0.758.Java 中 HashMap 的扩容机制是怎样的9.为什么 HashMap 在 Java 中扩容时采用 2 的 n 次方倍10.数组和链表在 Java 中的区别是什么1.Java 中有哪些集合类请简单介绍。中一般用于暖场简单讲讲即可Java 集合框架Java Collections Framework是 Java 核心库中用于存储和操作数据集合的标准架构主要分为Collection单元素集合和Map键值对集合两大接口体系。CollectionMap图片来源1Collection 接口单元素集合List 接口有序、可重复类名数据结构线程安全特点ArrayList动态数组❌随机访问快增删中间元素慢LinkedList双向链表❌增删快随机访问慢实现了Deque接口Vector动态数组✅线程安全但性能差已过时Stack栈继承Vector✅后进先出LIFOCopyOnWriteArrayList动态数组 写时复制✅读操作无锁写操作复制整个数组适合读多写少Set 接口无序、唯一类名数据结构线程安全特点HashSet哈希表❌基于HashMap实现查询最快LinkedHashSet哈希表 双向链表❌保持插入顺序TreeSet红黑树❌元素自动排序需实现ComparableCopyOnWriteArraySet数组 写时复制✅基于CopyOnWriteArrayListConcurrentSkipListSet跳表✅有序且线程安全高并发场景Queue 接口队列类名数据结构线程安全特点LinkedList双向链表❌作为队列使用支持FIFO操作PriorityQueue堆小顶堆❌元素按优先级排序自然序或自定义比较器ArrayDeque循环数组❌双端队列性能优于LinkedListConcurrentLinkedQueue链表✅无锁并发队列CAS 实现2Map 接口键值对集合类名数据结构线程安全特点HashMap数组 链表/红黑树❌最常用允许null键/值LinkedHashMap哈希表 双向链表❌保持插入顺序或访问顺序LRU 缓存基础TreeMap红黑树❌键自动排序基于红黑树Hashtable哈希表✅过时被ConcurrentHashMap取代ConcurrentHashMap数组 链表/红黑树 CAS✅高并发推荐分段锁/桶锁优化3如何选择需要键值对→HashMap非并发、ConcurrentHashMap并发需要有序→LinkedHashMap插入序、TreeMap排序去重存储→HashSet队列/栈→ArrayDeque双端队列、LinkedList栈高并发场景→ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue排序需求→TreeSet、TreeMap、PriorityQueue4常用声明声明左实现右场景ListTnew ArrayList()一般列表ListTnew LinkedList()频繁头插/删除SetTnew HashSet()去重MapK,Vnew HashMap()键值映射QueueTnew ArrayDeque()队列DequeTnew ArrayDeque()栈 / 双端队列DequeTnew LinkedList()需要存null时规律左边永远是接口或抽象父类右边是具体实现。2.Java 中的 List 接口有哪些实现类易Java 中的 List 接口是 Collection单元素容器 的子接口表示有序、可重复的集合。常见的实现类包括ArrayList, LinkedList, Vector, Stack, CopyOnWriteArrayList几个实现类。实现类底层结构线程安全适用场景ArrayList动态数组❌随机访问多增删少LinkedList双向链表❌频繁增删少量随机访问Vector动态数组✔️synchronized遗留代码不推荐新项目Stack继承Vector✔️栈结构推荐Deque替代CopyOnWriteArrayList动态数组COW✔️高并发读低并发写3.说说 Java 中 HashMap 的原理中HashMap基于数组链表/红黑树实现。存储键值对时先计算Key的hashCode再扰动处理然后(n-1)hash确定桶位置。如果桶为空直接放如果桶不为空则遍历链表/树用equals比较Key存在则覆盖Value不存在则添加新节点尾插法。当链表长度8且数组长度64时链表转红黑树树节点数6时树退化为链表。元素总数超过容量*负载因子(默认0.75)时会扩容通常2倍并重新哈希。查询时类似定位桶再遍历链表/树用equals查找。它允许null键值、无序、非线程安全理想情况下操作时间复杂度是O(1)。参考AI核心原理基于哈希表的键值对存储使用数组称为桶或bucket作为主干来存储数据。每个数组元素通常是一个链表的头节点Java 8后可能变为红黑树。put操作存储键值对计算哈希值调用键Key对象的hashCode()方法计算其哈希值。计算桶下标对哈希值进行特定的扰动计算Java 8使用(h key.hashCode()) ^ (h 16)来减少碰撞然后通过(数组长度 - 1) hash等价于hash % 数组长度但效率更高确定键值对应该存储在哪个桶数组索引。处理碰撞哈希冲突如果目标桶为空直接创建一个新节点包含Key, Value, hash放入该桶。如果目标桶不为空发生碰撞链表遍历桶中的链表或树用equals()方法比较新Key和链表中每个节点的Key如果找到相等的Key用新Value覆盖旧Value。如果没找到相等的Key将新节点添加到链表末尾Java 7是头插法Java 8改为尾插法。树化当链表长度超过阈值默认8且数组总长度达到一定大小默认64时该链表会转换为红黑树TreeNode以提高长链表下的查询效率O(n) - O(log n)。扩容如果添加元素后整个HashMap中元素的数量size超过了数组长度 * 负载因子默认负载因子loadFactor0.75则触发扩容resize创建一个新的、更大的数组通常是原长度的2倍。重新哈希遍历所有旧的桶和链表/树根据新的数组长度重新计算每个节点的桶下标并将节点迁移到新数组中。树退化在迁移过程中如果树中元素数量减少到阈值以下默认6红黑树会退化为链表。get操作根据键取值计算哈希值 桶下标与put操作相同的方式计算Key的哈希值和桶下标。遍历链表/树如果目标桶为空返回null。如果目标桶不为空如果桶中第一个节点链表头或树根的Key匹配equals直接返回其Value。否则遍历该桶上的链表或红黑树用equals()方法比较查找的Key和节点的Key找到匹配的Key返回对应Value。遍历完未找到返回null。关键特性无序迭代顺序不保证与插入顺序一致也不保证顺序不变。允许null键和null值。非线程安全多线程环境下并发修改可能导致死循环Java 7头插法导致、数据错乱或ConcurrentModificationException。需要外部同步如Collections.synchronizedMap或使用ConcurrentHashMap。性能在理想情况下无碰撞或碰撞少get和put操作的时间复杂度接近O(1)。最坏情况所有键都碰撞到同一个桶退化为链表是O(n)树化后提升为O(log n)。4.Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别中Java 7 的 ConcurrentHashMap 采用分段锁Segment实现默认16个段每个段独立加锁允许并发写入不同段但扩容和哈希冲突仍受段限制。Java 8 则抛弃分段锁改用CAS synchronized对单个桶Node加锁并引入红黑树优化哈希冲突扩容时支持多线程协同迁移并发度更高且内存开销更小。此外Java 8 新增函数式 API如 forEach、compute并优化了统计方法如size()使用 CounterCell 分散计数竞争。1.8 的实现更简洁高效锁粒度更细适应更高并发场景。特性JDK 1.7JDK 1.8数据结构Segment HashEntry 数组链表Node 数组 链表/红黑树锁粒度Segment 级别粗粒度桶级别细粒度锁实现ReentrantLockCAS synchronized哈希冲突处理链表O(n)链表转红黑树O(log n)扩容各 Segment 独立扩容多线程协同迁移数据适用场景写少读多高并发写入、大数据量5.为什么 JDK 1.8 对 HashMap 进行了红黑树的改动 中JDK 1.8 在 HashMap 中引入红黑树主要是为了解决哈希冲突严重时长链表导致的查询性能退化O(n)问题。当单个桶的链表长度8且数组长度64时链表会转换为红黑树将最坏情况下的操作时间复杂度从O(n)优化到O(log n)显著提升了高冲突场景下的性能和容器的抗攻击防哈希碰撞DoS能力。当链表长度6时红黑树会重新退化为链表保证低冲突时链表的效率优势。数据结构查找时间复杂度插入时间复杂度适用场景链表O(n)O(1)冲突较少红黑树O(log n)O(log n)冲突严重为什么阈值是8根据泊松分布哈希冲突达到8的概率小于千万分之一树节点占用空间是普通节点的两倍平衡性能与开销为什么需要最小树化容量64避免早期小规模哈希表的不必要树化优先通过数组扩容分散节点6.JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动 中1哈希函数优化扰动算法升级计算索引时新增一步hash key.hashCode() ^ (key.hashCode() 16)将高16位与低16位异或混合显著减少哈希冲突使元素分布更均匀2410。2链表插入方式改变头插法 → 尾插法JDK 1.7 使用头插法易导致多线程扩容死循环1.8 改为尾插法避免链表倒置提升并发安全性尽管仍非线程安全710。3扩容机制重构位置重计算优化扩容时不再全量重新哈希而是通过(e.hash oldCap) 0判断元素位置若为0索引不变若为1新索引 原索引 旧容量479。效率提升避免了重新计算哈希仅需一次位操作扩容性能大幅提高。4树化条件精细化链表转红黑树需同时满足链表长度 ≥TREEIFY_THRESHOLD默认8桶数组容量 ≥MIN_TREEIFY_CAPACITY默认64。否则优先扩容而非树化避免小表不必要的树结构开销569。5并发性能增强虽仍非线程安全但内部实现采用CAS 思想如size统计通过CounterCell分散竞争减少锁冲突12。特性JDK 1.7JDK 1.8数据结构数组 链表数组 链表 红黑树哈希计算直接取模高位扰动后取模插入方式头插法尾插法扩容开销全量重哈希位运算判断新位置树化逻辑无长度 ≥8 且容量 ≥64 才树化7.为什么 Java 中 HashMap 的默认负载因子是 0.75中Java 中HashMap的默认负载因子Load Factor设为0.75是经过严谨权衡的结果主要目的是在时间效率查询性能和空间效率内存利用率之间取得最佳平衡。既保障操作效率又避免内存浪费适合绝大多数场景。负载因子查询性能内存利用率适用场景0.5✅ 极高冲突少❌ 低浪费 50%内存充足、实时系统0.75✅平衡接近 O(1)✅ 较高闲置 25%默认场景1.0❌ 差冲突频繁✅ 高无浪费内存紧张、低频访问8.Java 中 HashMap 的扩容机制是怎样的中HashMap 在元素数超过容量×0.75时触发扩容新容量为当前容量的两倍。JDK 1.8 后通过高位比特判断节点新位置免重算哈希用尾插法拆分链表/树迁移数据避免死循环并提升效率。具体步骤创建新数组新容量 旧容量的 2 倍如 16 → 32保证容量始终为 2 的幂便于位运算优化。迁移数据遍历旧数组的每个桶Bucket重新分配每个节点到新数组JDK 1.8 优化关键通过e.hash oldCap(原数组长度的二进制数)高位比特判断位置无需重算哈希如16为10000看 e.hash 第五位是否为1结果为 0→ 节点留在原索引位置index不变。结果为 1→ 节点迁移到新索引 原索引 旧容量index oldCap。链表拆分若桶中是链表按高位结果拆分为两个链表原位置链表 新位置链表保持顺序尾插法。红黑树拆分若桶中是红黑树按相同逻辑拆分若拆分后节点数 ≤6则退化为链表。更新引用将新数组设置为HashMap的底层存储旧数组被 GC 回收。重新计算阈值新阈值 新容量 × 负载因子如 32 × 0.75 24。操作JDK 1.7JDK 1.8哈希重计算所有节点重新计算hash免重算用e.hash oldCap判断链表迁移头插法可能死循环尾插法避免闭环迁移效率单节点遍历迁移按高位结果批量迁移链表/树9.为什么 HashMap 在 Java 中扩容时采用 2 的 n 次方倍中HashMap 采用 2 的 n 次方容量核心是通过(n-1) hash位运算替代取模极大提升计算桶下标的效率同时支持扩容时免重算哈希仅需高位比特判断新位置n hash降低迁移开销并提升哈希分布的均匀性是性能与设计优雅性的双重优化。具体分析高效计算桶下标核心优化定位桶下标公式index (n - 1) hashn为数组长度。当n为 2 的幂时n - 1的二进制全为1例如16-115 → 1111。位运算替代取模(n-1) hash等价于hash % n但位运算比取模快 10 倍以上CPU 指令级优化。扩容时免重算哈希JDK 1.8 优化扩容后新下标 原位置或原位置 旧容量index或index oldCap。判断逻辑直接通过e.hash oldCap的高位比特0 或 1决定位置无需重新计算hash值。例如旧容量16二进制10000若e.hash 16 0则位置不变否则新位置 原位置 16。减少哈希冲突分布更均匀如果length不是 2 的幂次方(length - 1)的二进制会有0位如length 15→1110导致某些index永远无法被计算到如0001增加哈希冲突概率。2 的幂次方长度能更均匀分布元素(n-1) hash确保哈希值的所有有效位都参与计算提高查询效率。特性2^n 容量非 2^n 容量计算桶位置速度1 CPU 周期10 CPU 周期哈希分布均匀性最优全低位参与部分桶位不可用扩容元素迁移位判断O(1)全量重哈希O(n)内存利用率100% 有效最高 87.5%10.数组和链表在 Java 中的区别是什么中数组基于连续的内存块且大小固定支持O(1) 随机访问但增删成本高链表基于节点通过指针动态链接增删 O(1)但访问需 O(n)。在 Java 中ArrayList基于数组适合读多写少LinkedList基于链表适合频繁增删场景。特性数组链表内存结构连续内存块非连续内存节点分散存储访问效率O(1)通过下标直接寻址O(n)需从头遍历增删效率O(n)需移动后续元素O(1)仅修改指针无需移动内存占用固定大小初始化后不可变动态扩容按需增删节点内存开销仅存储数据额外存储指针next/prev适用场景频繁随机访问、数据量固定频繁增删、数据量动态变化本文到此结束如果对你有帮助可以点个赞~后续会在合集里持续更新 Java 相关的面试题欢迎关注~祝各位都能拿到满意的offer~
返回列表