资讯详情

Java集合框架核心:ArrayList、HashMap与ConcurrentHashMap解析

📅 2026/9/13 11:58:22 | 华诺云谱 👁 阅读
Java集合框架核心:ArrayList、HashMap与ConcurrentHashMap解析
1. 项目概述谢飞机大厂面试记从懂王到回家等通知的Java进阶之路这个标题生动描述了一个Java开发者在大厂面试中的心路历程。通过分析相关热搜词和网络热词我们可以看出这篇文章的核心将围绕Java集合框架的面试考点展开特别是ArrayList、HashMap和ConcurrentHashMap这三个关键数据结构的底层实现原理、线程安全性以及实际应用场景。2. 核心面试题解析2.1 ArrayList的线程安全问题ArrayList是非线程安全的集合类这主要体现在它的add()方法没有同步机制。当多个线程同时修改ArrayList时可能会出现以下三种典型问题部分值为null当线程1执行add操作时CPU时间片用完线程2也执行add操作可能导致元素覆盖或size计数不准确数组越界异常在扩容过程中多个线程同时操作可能引发ArrayIndexOutOfBoundsExceptionsize与实际元素数量不符由于size不是原子操作可能导致计数错误// 非线程安全的add方法实现 public boolean add(E e) { ensureCapacityInternal(size 1); // 检查容量 elementData[size] e; // 非原子操作 return true; }注意使用Collections.synchronizedList()包装ArrayList或者改用CopyOnWriteArrayList可以解决线程安全问题2.2 HashMap的底层实现JDK8中的HashMap采用数组链表红黑树的结构哈希计算通过key的hashCode()计算哈希值索引定位使用(n-1) hash确定桶位置冲突解决链表当哈希冲突时形成链表拉链法红黑树当链表长度≥8且数组长度≥64时转换为红黑树// JDK8 HashMap的put方法核心逻辑 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { NodeK,V[] tab; NodeK,V p; int n, i; if ((tab table) null || (n tab.length) 0) n (tab resize()).length; if ((p tab[i (n - 1) hash]) null) tab[i] newNode(hash, key, value, null); else { // 处理哈希冲突... } modCount; if (size threshold) resize(); return null; }2.3 ConcurrentHashMap的线程安全实现JDK8的ConcurrentHashMap通过以下机制保证线程安全CASsynchronized只在链表头节点或红黑树根节点加锁分段计数使用CounterCell数组避免size()的竞争扩容协助多线程可以协助完成扩容操作// JDK8 ConcurrentHashMap的putVal方法片段 final V putVal(K key, V value, boolean onlyIfAbsent) { if (key null || value null) throw new NullPointerException(); int hash spread(key.hashCode()); int binCount 0; for (NodeK,V[] tab table;;) { NodeK,V f; int n, i, fh; if (tab null || (n tab.length) 0) tab initTable(); else if ((f tabAt(tab, i (n - 1) hash)) null) { if (casTabAt(tab, i, null, new NodeK,V(hash, key, value))) break; } // ...其他处理逻辑 } }3. 面试常见问题深度解析3.1 ArrayList与LinkedList区别特性ArrayListLinkedList底层结构动态数组双向链表随机访问效率O(1)O(n)头尾插入删除效率尾部O(1)头部O(n)头尾都是O(1)内存占用连续内存节省空间每个元素需要额外节点指针迭代器性能快速随机访问顺序访问优化3.2 HashMap的扩容机制HashMap扩容的关键步骤触发条件当size capacity * loadFactor默认0.75新容量原数组长度的2倍数据迁移JDK7重新计算每个元素的位置JDK8利用高位判断元素要么在原位置要么在原位置旧容量// JDK8的resize()方法核心逻辑 final NodeK,V[] resize() { NodeK,V[] oldTab table; int oldCap (oldTab null) ? 0 : oldTab.length; int newCap oldCap 1; // 容量翻倍 // ...省略阈值计算等代码 NodeK,V[] newTab (NodeK,V[])new Node[newCap]; table newTab; if (oldTab ! null) { for (int j 0; j oldCap; j) { NodeK,V e; if ((e oldTab[j]) ! null) { oldTab[j] null; if (e.next null) newTab[e.hash (newCap - 1)] e; else if (e instanceof TreeNode) ((TreeNodeK,V)e).split(this, newTab, j, oldCap); else { // 保持顺序的链表迁移 NodeK,V loHead null, loTail null; NodeK,V hiHead null, hiTail null; NodeK,V next; do { next e.next; if ((e.hash oldCap) 0) { if (loTail null) loHead e; else loTail.next e; loTail e; } else { if (hiTail null) hiHead e; else hiTail.next e; hiTail e; } } while ((e next) ! null); // ...将高低位链表放入新数组 } } } } return newTab; }3.3 ConcurrentHashMap的size计算ConcurrentHashMap采用分片计数的方式避免竞争baseCount基础计数器通过CAS更新CounterCell[]当CAS更新baseCount失败时使用分片计数最终sizebaseCount ∑CounterCell.value// JDK8的size()实现 public int size() { long n sumCount(); return ((n 0L) ? 0 : (n (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)n); } final long sumCount() { CounterCell[] as counterCells; CounterCell a; long sum baseCount; if (as ! null) { for (int i 0; i as.length; i) { if ((a as[i]) ! null) sum a.value; } } return sum; }4. 面试实战技巧4.1 如何回答HashMap工作原理建议采用结构化回答基本结构数组链表红黑树哈希计算hashCode()的高16位异或低16位索引定位(n-1) hash冲突解决链表→红黑树转换扩容机制2倍扩容高位判断迁移线程安全非线程安全推荐ConcurrentHashMap4.2 ArrayList迭代修改的正确方式避免ConcurrentModificationException的几种方式ListString list new ArrayList(Arrays.asList(a, b, c)); // 错误方式 - 会抛异常 for (String s : list) { if (s.equals(b)) { list.remove(s); // ConcurrentModificationException } } // 正确方式1 - 使用Iterator IteratorString it list.iterator(); while (it.hasNext()) { if (it.next().equals(b)) { it.remove(); // 安全删除 } } // 正确方式2 - 使用CopyOnWriteArrayList ListString cowList new CopyOnWriteArrayList(list); for (String s : cowList) { if (s.equals(b)) { cowList.remove(s); // 安全操作 } } // 正确方式3 - 使用下标仅适用于特定场景 for (int i 0; i list.size(); i) { if (list.get(i).equals(b)) { list.remove(i--); // 调整索引 } }4.3 设计线程安全的缓存系统结合ConcurrentHashMap的特性设计缓存public class CacheK, V { private final ConcurrentHashMapK, V map new ConcurrentHashMap(); private final ConcurrentHashMapK, Long expireTimes new ConcurrentHashMap(); private final ScheduledExecutorService cleaner Executors.newSingleThreadScheduledExecutor(); public Cache() { cleaner.scheduleAtFixedRate(this::cleanExpired, 1, 1, TimeUnit.SECONDS); } public void put(K key, V value, long ttl, TimeUnit unit) { map.put(key, value); expireTimes.put(key, System.currentTimeMillis() unit.toMillis(ttl)); } public V get(K key) { Long expireTime expireTimes.get(key); if (expireTime ! null expireTime System.currentTimeMillis()) { return map.get(key); } return null; } private void cleanExpired() { long now System.currentTimeMillis(); expireTimes.forEach((key, expireTime) - { if (expireTime now) { map.remove(key); expireTimes.remove(key); } }); } }5. 高频面试问题清单5.1 ArrayList相关问题ArrayList的默认初始容量是多少10ArrayList的扩容机制是怎样的1.5倍增长如何将ArrayList转换为线程安全的Collections.synchronizedListArrayList的迭代器是快速失败的吗是ArrayList和Vector的主要区别是什么Vector线程安全但性能低5.2 HashMap相关问题HashMap的负载因子默认值是多少0.75为什么链表长度超过8要转红黑树提高查询效率HashMap为什么不是线程安全的并发修改会导致数据不一致HashMap的key可以为null吗可以放在第0个桶HashMap的哈希算法是如何实现的((h key.hashCode()) ^ (h 16))5.3 ConcurrentHashMap相关问题JDK7和JDK8的ConcurrentHashMap实现有什么区别分段锁 vs CASsynchronizedConcurrentHashMap的size()方法是准确的吗是通过分片计数ConcurrentHashMap的读操作需要加锁吗不需要使用volatile保证可见性为什么ConcurrentHashMap不允许null键值避免二义性ConcurrentHashMap的扩容机制是怎样的多线程协助扩容6. 面试准备建议理解原理不仅要记住答案更要理解设计思想手写代码练习实现简单的ArrayList/HashMap对比分析比较不同集合类的优缺点实战演练模拟面试场景进行练习关注变化了解不同JDK版本的实现差异我在实际面试中发现面试官往往更看重候选人能否清晰地表达技术原理而不是死记硬背答案。建议在准备时多画图辅助理解比如HashMap的put过程、ConcurrentHashMap的分段锁机制等这样在面试中能够更直观地展示你的理解深度。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。