ARTICLE DETAIL

资讯详情

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

Java集合面试核心考点:HashMap与ConcurrentHashMap底层原理全解析

Java集合面试核心考点:HashMap与ConcurrentHashMap底层原理全解析 每年面试季Java集合这块都是必考的重头戏。不管是校招还是社招几乎每一轮技术面都会从集合切入——HashMap的底层原理、ArrayList和LinkedList的区别、ConcurrentHashMap的线程安全实现这些题目就像面试的“基本功考试”答不好后面聊得再嗨也白搭。我见过不少候选人项目经验挺扎实结果栽在集合的追问上一问底层就卡壳很可惜。所以这篇就把Java集合面试里最高频、最容易踩坑的考点一次性梳理清楚。我会按面试官习惯的考察路径来组织先搭整体框架再逐个击破List、Map、Set重点讲HashMap和ConcurrentHashMap的底层实现最后补充fail-fast机制和几个日常编码中特别容易出问题的细节。每一块都尽量从“面试官为什么会问这个”的角度去讲把原理、源码、坑位都点透保证你面完这一篇心里有底。1. 集合面试的整体框架先画地图再背细节Java集合这块面试官考察的其实就两件事第一你知不知道有什么第二你知不知道为什么。前者考察你对集合框架的熟悉程度后者考察你对底层实现的真实理解。很多人挂在第二关就是因为只记住了“是什么”不知道“为什么”。先看整体结构。Java集合框架主要分为两个体系Collection和Map。Collection下面又有List、Set、Queue三个子接口Map是独立的一套键值对体系。面试官特别喜欢让你从顶层接口开始说层层往下讲看你脑子里有没有一张完整的地图。我把面试里最常见的考察点整理成一个速查表方便你对照复习集合类底层结构线程安全排序性高频考点ArrayList动态数组否按插入序扩容机制、与LinkedList对比LinkedList双向链表否按插入序底层结构、插入删除效率Vector动态数组是synchronized按插入序与ArrayList区别、已经过时HashSetHashMap否无序为什么用HashMap实现LinkedHashSetLinkedHashMap否按插入序怎么做到有序的TreeSetTreeMap红黑树否自然序/定制序排序原理HashMap数组链表红黑树否无序高频中的高频必问底层LinkedHashMapHashMap双向链表否按插入序/访问序实现LRUTreeMap红黑树否按key排序与HashMap对比ConcurrentHashMap数组链表红黑树是CASsynchronized无序线程安全实现、JDK1.7与1.8差异这个表你要是能不看资料自己默写出来说明基础框架已经上了。下面每个核心类我都会展开讲面试官最爱的追问点。2. List篇ArrayList与LinkedList对决2.1 底层实现与时间复杂度对比“ArrayList和LinkedList有什么区别”这道题几乎每场面试必出没跑。如果你只知道“一个底层是数组一个是链表”那只能算及格面试官很快会深入问下去。ArrayList底层是Object数组所以它支持随机访问通过下标取元素是O(1)的复杂度。而LinkedList底层是双向链表每个节点都持有前驱和后继的引用要访问第n个元素得从头部或尾部开始遍历时间复杂度是O(n)。插入和删除呢这里有很多人答反了。很多人条件反射地说“LinkedList插入删除快ArrayList慢”这话只说对了一半。LinkedList的插入确实只需要修改指针O(1)搞定位但前提是你已经定位到了那个节点。实际场景下如果按索引插入LinkedList还是要先遍历找到那个位置复杂度还是O(n)。而ArrayList在尾部插入是O(1)只有在中间插入时才需要搬移后续元素是O(n)。所以更准确的说法是LinkedList适合频繁在头部或中间做插入删除、且不依赖随机访问的场景ArrayList适合读多写少、或者只在尾部追加的场景。而且绝大多数业务代码里ArrayList的表现反而优于LinkedList原因后面我会讲。还有一个细节LinkedList还实现了Deque接口可以当双端队列用支持addFirst、addLast这些操作。面试官可能会顺嘴问一句“LinkedList能实现栈吗”答案是能官方文档推荐用ArrayDeque来实现栈因为它不需要维护节点对象内存更紧凑性能更好。2.2 ArrayList扩容机制细节ArrayList扩容是八股文里的经典题。基础版本大家都知道默认初始容量是10每次扩容为原来的1.5倍。但我劝你尽量把源码细节也答出来因为面试官想听到的不是一个数字而是你对机制的理解。用无参构造创建ArrayList时其实底层是一个空数组只有在第一次add元素时才真正初始化为容量10的数组。这是一个懒加载的设计避免创建了对象却不使用就白白占用内存。扩容发生时会计算出新容量oldCapacity (oldCapacity 1)也就是老的1.5倍。然后调用Arrays.copyOf把老数组的元素整体拷贝到新数组里。这里有一个细节值得注意如果oldCapacity是奇数右移一位会向下取整比如11扩容后是16而不是16.5因为容量必须是整数。为什么是1.5倍而不是两倍这也可能是加分项的回答角度。1.5倍的扩容策略在空间和时间上做了权衡扩容倍数太低会导致频繁扩容、频繁拷贝浪费性能倍数太高比如直接三倍内存浪费明显。1.5倍是比较经典的折中方案既保证一定的扩容频率控制又不会让太多空间闲置。2.3 Arrays.asList与subList的坑面试官问集合的坑有一半都会落在Arrays.asList和subList上这两个都是实际开发中特别容易埋雷的地方。先说Arrays.asList它的返回值是一个Arrays类的内部类ArrayList不是java.util.ArrayList。这个内部类直接复用了传入的数组不进行拷贝所以它有两个硬伤第一不能调用add和remove方法会抛UnsupportedOperationException因为并没有实现这两个方法第二修改返回的List原数组也会跟着变反之亦然。再说subList这个坑更深。subList返回的是原List的一个视图不是复制出来的新List。对subList做的修改会直接反映到原List上。更危险的是如果在subList操作期间原List发生了结构性修改比如add或remove再操作subList会抛出ConcurrentModificationException。我工作中踩过一次很深的坑用subList做分页然后对子列表做批量删除结果把原列表的数据也删了。排查了挺久才意识到是视图共享的问题。所以面试的时候你如果能主动讲出这个经验比干巴巴背八股强多了至少说明你真的写过代码、真的踩过坑。3. HashMap面试必被深挖的核心3.1 底层结构与hash定位原理HashMap是整个Java集合面试里的重中之重没有之一。面试官对HashMap的考察几乎是穷尽式的从数据结构到put流程从扩容机制到并发问题可以连环追问十几分钟不重样。这一块值得你多花时间。先说底层结构。JDK 1.8之后HashMap的底层是数组链表红黑树。数组也叫桶数组每个桶的位置要么是null要么是单个节点要么是链表要么是红黑树。为什么要引入红黑树因为当多个元素哈希冲突严重导致某个桶里链表很长时查询复杂度会退化成O(n)。红黑树能保证最坏情况下的查找复杂度是O(log n)所以链表长度超过阈值8并且数组容量达到64时链表会转换为红黑树。接下来是hash定位。很多人以为HashMap就是直接用hashCode做下标其实不是。key的hashCode先经过一次扰动h key.hashCode() ^ (h 16)也就是把高16位和低16位做异或。这样做的目的是让高16位的特征也能参与到低位的计算中减少冲突概率。然后计算数组下标tab[i (n - 1) hash]。这里用位运算而不是取模前提是数组长度必须是2的幂。所以HashMap每次扩容都是翻倍就是为了保持长度是2的幂让位运算能替代取模运算效率更高。这也解释了为什么HashMap对容量有“必须是2的幂”这个隐含要求——即使你传入一个不是2的幂的初始容量它也会向上取整到最近的2的幂。3.2 put流程完整走一遍面试官让你“说说HashMap的put过程”这几乎是必考题。我建议你把源码层面的完整流程背下来但不要死背而是理解每个分支为什么存在。第一步对key计算hash值经过扰动处理。第二步判断桶数组table是否为null或者长度是否为0是则先resize初始化。第三步用hash和(n-1)做与运算得到数组下标如果该位置为null直接newNode放进去。第四步如果该位置不为null说明发生冲突先判断头节点是否和key相同即hash相等且equals相等是则覆盖value。第五步如果头节点是TreeNode类型说明该桶已经是红黑树走红黑树的插入逻辑。第六步否则说明是链表遍历链表如果找到相同的key就覆盖value并返回旧值如果遍历到尾部还没找到就在尾部插入新节点。第七步插入完成后检查链表长度是否超过8超过则调用treeifyBin尝试转红黑树但treeifyBin内部还会判断数组长度是否到64不到就先扩容。第八步最后检查size是否超过threshold超过则resize。有个细节很多人容易漏第五、六步里如果找到相同key并覆盖value这个操作是返回旧value的而且不会触发后面的扩容判断因为size并没有改变。这个点在面试官的追问里经常出现。还有一个容易记混的地方JDK 1.7是头插法新节点插在链表头部JDK 1.8改成尾插法新节点接在链表尾部。为什么要改主要是因为头插法在并发扩容时会形成环形链表导致get时死循环。虽然HashMap本来就不保证线程安全但1.7这个bug实在太出名了面试官会特意拿来考察你对并发问题的理解深度。3.3 扩容、树化、退化机制HashMap的扩容机制也是重点。默认的负载因子是0.75threshold 容量 × 负载因子。当元素个数超过threshold时触发扩容新容量是旧容量的两倍同时threshold也翻倍。扩容的过程是创建一个新数组然后把旧数组上的所有元素重新散列到新数组上。为什么负载因子是0.75而不是0.5或者1这是空间和时间的一个平衡。负载因子太大比如1虽然空间利用率高但冲突概率也随之上升查询效率会明显下降负载因子太小比如0.5虽然冲突少、查询快但空间浪费严重动不动就扩容。0.75是官方在大量测试后给出的一个较优值。在不能确定元素数量时这是一个默认推荐的设置我们能做的就是了解它的设计思路。关于树化的两个条件要记牢链表长度大于等于8且数组长度大于等于64。两个条件必须同时满足否则不会树化。如果只是链表长度到了8但数组长度还没到64HashMap会优先扩容通过扩容来打散链表。为什么要阈值设成8官方注释里给了一个泊松分布的推算在负载因子0.75、随机hashCode的条件下链表长度达到8的概率大约是千万分之六非常低。设成8是为了保证树化只发生在极端冲突的情况下平时大部分时间链表都很短没必要树化。反过来当红黑树的节点数少到6个时会退化成链表这样在频繁增删的场景下可以避免树和链表之间来回切换的性能开销。3.4 为什么HashMap线程不安全面试官问“HashMap线程安全吗”标准答案是不安全。但你要能说出具体不安全在哪几个方面这才显出功底。第一数据覆盖问题。两个线程同时put恰好都定位到同一个桶位置而且该位置是null两个线程都会newNode然后赋值给该位置后赋值的会把先赋值的覆盖掉导致丢失更新。这个在JDK 1.7和1.8里都存在。第二size计数不准确。HashMap的size字段是普通的int没有原子性保护。多个线程同时put或remove时size的增减可能相互覆盖导致最终的size和实际元素数不一致。第三JDK 1.7中的扩容死循环。这个前面提到过头插法在并发扩容时可能导致链表成环get操作会陷入无限循环。JDK 1.8改成尾插法后这个问题不复存在但数据覆盖和size不准的问题依然在。所以面试官如果问“那我想用线程安全的Map怎么办”你要能给出三个选项并说明场景ConcurrentHashMap高并发首选、Hashtable全方法加锁并发效率低已过时、Collections.synchronizedMap包装类原理也是整体加锁。多数场景下直接选ConcurrentHashMap就行。4. ConcurrentHashMap与并发集合的演进4.1 JDK 1.7的分段锁设计讲ConcurrentHashMap最好从版本演进讲起因为面试官很想听到“为什么1.8要抛弃1.7的设计”这个层面的思考。JDK 1.7的ConcurrentHashMap采用的是Segment分段锁的架构。底层是一个Segment数组每个Segment继承自ReentrantLock可以看作一个小的Hashtable。默认是16个Segment所以理论上并发度是16也就是说同时可以有16个线程各自操作不同的Segment而互不竞争。这种设计的优点是并发度比Hashtable整体加锁高得多缺点是分段不均匀会导致某些Segment变成热点而且一旦需要扩容一个Segment只能内部自己扩容不能整个Map一起扩。另外定位一个元素需要两次hash第一次定位到Segment第二次定位到Segment内部的桶位置。4.2 JDK 1.8的CASsynchronized改进JDK 1.8对ConcurrentHashMap做了大刀阔斧的重构彻底抛弃了Segment直接用Node数组链表红黑树和HashMap的数据结构几乎一致只是在并发控制和关键节点上做了额外处理。put流程上1.8采用CAS synchronized的组合。具体来说计算hash后定位到桶位置如果该位置为null就用CAS直接放入不需要加锁如果该位置不为null就用synchronized锁住这个桶的头节点然后进入链表或红黑树的插入逻辑。这样锁的粒度从1.7的Segment级别细化到了单个桶级别并发度进一步提升。为什么1.8选择synchronized而不是ReentrantLock这是面试官常问的一个点。诚实的回答是synchronized在JDK 1.6之后做了大量优化偏向锁、轻量级锁、锁升级性能已经不输ReentrantLock而且synchronized不需要手动释放锁代码上更简洁出错概率更低。在桶级别的细粒度场景下synchronized的方案既简单又高效官方自然就这么选了。还有一个独特的点是扩容协助机制。1.8的扩容不是单线程完成的而是多线程可协助的。扩容时会把任务分片参与操作的线程在put或get时如果发现正在扩容可以帮忙迁移一部分节点而不是干等着。这个机制也是面试加分项知道它就能说明你对源码的阅读不是停留在表面。4.3 并发场景还该知道哪些集合除了ConcurrentHashMap并发的集合类还有几个面试官可能会顺带考察至少要知道它们存在的意义。CopyOnWriteArrayList写时复制的List适用于读多写极少、数据量不大的场景。它读的时候不加锁写的时候加锁然后把整个底层数组复制一份在新数组上修改最后把引用指向新数组。缺点是每次写都有数组拷贝的开销频繁写会很浪费。CopyOnWriteArraySet底层就是一个CopyOnWriteArrayList用于去重并且写少的并发场景。ConcurrentLinkedQueue无界非阻塞队列基于CAS实现适合生产者消费者模式中吞吐量要求高的场景。BlockingQueue是一族阻塞队列实现类有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue等在线程池里被大量使用。如果你要说线程池那LinkedBlockingQueue和ArrayBlockingQueue的区别就是必考题。5. Set与排序容易被小看的高频考点5.1 HashSet的实现套路很多人背过HashSet的结论底层是HashMap。但面试官如果追问一句“它怎么用HashMap实现的”一时还是有不少人答不上来。HashSet内部确实持有HashMap实例它添加元素时是把元素作为HashMap的key然后所有value统一用一个固定值——一个静态的Object常量PRESENT。所以HashSet的底层就等于HashMap的key那一层去重依靠的就是HashMap的key不能重复这个特性。这个考点本身就很有代表性Java里很多类的实现是“组合复用”不是每个功能都从零写。HashSet和HashMap的关系就是一个典型的组合模式案例理解了这一点你对LinkedHashSet的问题也顺带懂了——LinkedHashSet是HashSet的子类底层用的是LinkedHashMap这样既能去重又能维护插入顺序。另一个高频追问是equals和hashCode的关系。HashSet判断两个元素是否重复会先用hashCode定位桶再通过equals判断是否相等。所以如果你往HashSet里放了自定义对象却不重写equals和hashCode即使两个对象的业务字段完全一样也会被认为是两个不同的元素。这个坑在开发中很常见面试里也经常作为场景题出现。5.2 TreeMap/TreeSet的排序逻辑TreeMap和TreeSet是另一对需要了解的组合。TreeMap底层是红黑树key会按照自然顺序或者你传入的Comparator进行排序。TreeSet底层是TreeMap和HashSet用HashMap是同一套路。这里要理解两个概念自然排序和定制排序。自然排序是让key实现Comparable接口重写compareTo方法定制排序是在创建TreeMap时传入一个Comparator实现。两者不能同时存在如果同时用了定制排序优先。红黑树的时间复杂度是O(log n)所以TreeMap的get、put、remove都是O(log n)比HashMap的O(1)慢但它的优势在于key是有序的可以方便地获取最小key、最大key、按区间遍历等。面试中经常让你对比HashMap和TreeMap核心维度就三个顺序性、时间复杂度、是否允许null。需要注意的是TreeMap不能有null的key因为红黑树需要基于key进行比较排序null无法比较。而HashMap允许一个null key因为它在存储null时统一放到下标0的桶里。5.3 Comparable与Comparator答题要点Comparable和Comparator的区别是基础中的基础但面试官会变着花样问。最简单的问法是“说说区别”你至少要答出三四个层次才能算过关。第一包路径不同Comparable是java.lang包下的Comparator是java.util包下的。第二位置不同Comparable是让类自己实现是“自身具备比较能力”Comparator是单独写一个比较器是“外部定义比较规则”。第三方法不同Comparable只有一个compareTo方法Comparator有compare方法而且从JDK 8开始还有一堆默认方法比如thenComparing、reversed可以链式组合多个比较规则。从设计角度说如果一个类的自然排序是稳定的、明确的比如Integer的自然排序就是按数值大小那就用Comparable如果同一个类有不同的排序需求比如员工按年龄排、按工资排、按工号排那Comparable只能有一种实现剩下就得靠多个Comparator来实现。在面试里如果被问到排序你可以顺手补一句Collections.sort内部调用的其实是List.sort而List.sort在JDK 8之后默认走的是Arrays.sort对对象数组用的是TimSort算法对基本类型数组用的是双轴快速排序。能说清楚这个调用链的人不多属于高阶加分项。5.4 快速排序与冒泡排序的Java实现集合排序里经常捎带考察排序算法尤其是冒泡排序和快速排序这俩属于手写题里的常客。我这里把基础版本放出来你自己跑一遍顺便思考一下每个步骤的时间复杂度。冒泡排序的Java实现核心思想是相邻元素两两比较大的往后挪public static void bubbleSort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { // 加个标志位如果某轮没有交换说明已经有序提前结束 boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped true; } } if (!swapped) { break; } } }快速排序的Java实现核心是分治基准值public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot arr[left]; int i left; int j right; while (i j) { while (i j arr[j] pivot) { j--; } while (i j arr[i] pivot) { i; } if (i j) { int tmp arr[i]; arr[i] arr[j]; arr[j] tmp; } } arr[left] arr[i]; arr[i] pivot; quickSort(arr, left, i - 1); quickSort(arr, i 1, right); }手写算法的时候有几个细节容易出问题递归的终止条件别忘了写快排的双指针扫描右边先动还是左边先动取决于基准值选取的位置你要是基准值取left那就必须先让right指针向左找小于基准值的元素否则正确性会出问题。面试手写时我建议先选一个简单的不容易出错的算法比如递归快排加数组copy虽然性能差一点但不容易写错。6. fail-fast机制与日常编码中的集合坑6.1 modCount与ConcurrentModificationExceptionfail-fast机制直译是“快速失败”是Java集合框架里的一个安全设计。它的原理是集合内部维护了一个modCount字段记录结构修改次数add、remove、clear等会导致结构性修改的操作都会让modCount加1。迭代器创建时会保存当前的modCount作为expectedModCount每次迭代都会检查这两个值是否一致不一致就抛出ConcurrentModificationException。这个机制的目的是尽早暴露并发修改问题而不是等到出现不确定的结果才报错。在单线程下如果你在遍历一个ArrayList的同时调用add或者remove也会触发这个异常。很多初学者遇到这个异常一脸懵其实记住一句话就够了不要在foreach循环里直接改集合结构。要特别留神的是这个检查是在迭代器的next方法里做的所以有时候你改了之后报错的位置可能隔了几次循环才出现看起来像是随机崩溃其实是必然的。6.2 遍历时安全删除的正确姿势那遍历时想删元素怎么办这是面试里经常出的场景题。有几种安全的方式我按推荐度排个序。第一使用迭代器自己的remove方法IteratorString iterator list.iterator(); while (iterator.hasNext()) { String item iterator.next(); if (需要删除的.equals(item)) { iterator.remove(); } }这才是foreach循环在语法层面隐藏的那个迭代器区别在于调用的是迭代器自己的remove它会同步更新modCount和expectedModCount所以不会触发fail-fast异常。第二使用JDK 8的removeIf方法list.removeIf(item - 需要删除的.equals(item));这是最推荐的方式一行代码搞定内部就是用迭代器实现的安全又简洁。第三倒序遍历索引删除for (int i list.size() - 1; i 0; i--) { if (需要删除的.equals(list.get(i))) { list.remove(i); } }倒序删除不会影响前面元素的下标所以不会漏删也不用担心索引越界。这个方式虽然能用但不够优雅应付笔试时可以写。我在面试里见过不少候选人表达上会说“用迭代器删除”但真让他手写时写的却是forEach里调list的remove方法这就是理论和实践的差距。建议自己把上面三种方式都跑一遍感受一下异常到底在哪行触发。6.3 集合判空、初始化容量等编码建议集合这块的编码规范也是面试中可能出现的“软问题”面试官不直接问八股而是让你评价一段代码或者让你改进。第一判断集合是否为空不要用list.size() 0因为可能为null导致空指针。正确姿势是有判断工具类就用比如org.apache.commons.collections.CollectionUtils.isEmpty或者JDK 9之后的List.of否则就手动判断null。很多人写代码习惯只判断size结果线上环境被空指针搞了一次才长记性。第二使用HashMap时建议指定初始容量。如果已知数据量大约是10000直接new HashMap(10000)只是字面上对实际上HashMap会把容量向上取整到2的幂也就是16384而不是10000。如果你不想触发扩容initialCapacity应该按照“数据量 / 负载因子 1”来估算。这是很多大厂面试中问得比较刁钻的细节。第三Arrays.asList的返回值不能增删这个问题前面说过开发中也是高频踩坑点。建议创建可变集合时直接new ArrayList(Arrays.asList(...))包一层。第四集合里存的如果是自定义对象一定要确认equals和hashCode是否重写否则去重、包含判断、Map的key都会出问题。7. 面试答法怎么把八股说成自己的经验7.1 面试官追问的常见套路背八股最大的问题是你背的是标准答案面试官问的是真实场景两者一旦错位就露馅了。我总结几个常见追问你可以提前演练。第一“你刚才说HashMap在链表长度超过8时会转红黑树为什么不一开始就用红黑树”这个问题考的是你对红黑树特性的理解。红黑树虽然查询快但每个节点需要用额外的指针维护父子关系内存占用比链表节点大而且插入和删除时需要旋转来维持平衡吞吐量反而不如链表。所以只在冲突严重时才转避免常态化的额外开销这就是“空间换时间但只在必要时换”的思想。第二“集合里的元素是自定义对象你重写equals但没重写hashCode会发生什么”这个问题的坑在于违反了equals和hashCode的约定equals相等要求hashCode必须相等。如果你只重写equals两个equals相等的对象可能hashCode不同在HashSet或HashMap里会被分到不同桶导致equals判断完全失效。第三“HashMap的key如果是null它放在哪里”答案是数组下标0的位置。因为null无法计算hashCodeHashMap内部做了特殊处理把null的hash直接当成0。而ConcurrentHashMap不允许null的key和value目的是避免多线程下二义性——线程读到null时无法区分是key不存在还是value本来就为null。第四“如果让你设计一个LRU缓存你会怎么做”这道题LinkedHashMap的默认实现就是很好的答案基础。LinkedHashMap维护了一个双向链表记录访问顺序构造时传入accessOrdertrue每次get会把节点移动到尾部这样头部就是最久未使用的容量满了就删除头节点。7.2 个人经验准备这一块的方法论集合这块我面试过不少人也反复研究过自己的学习方法有几点心得想分享。第一八股一定要结合源码背。你直接背“链表长度超过8转红黑树”很容易但面试官一问到机制里的细节就暴露了。真正的做法是把HashMap那几个核心方法的源码读一遍逐行理解每一个分支把过程真正吃透这样无论面试官怎么换着花样问你都能用源码逻辑兜底。第二把集合串成一条线来记忆。不要孤立地背每个类而是从ArrayList到Vector、从HashMap到Hashtable到ConcurrentHashMap把“为什么会出现后面这个类”作为主线你就自然理解了整条演进逻辑。面试官要的也是这种抽象能力。第三动手写一遍核心场景的错误代码。比如故意在foreach里删除元素触发ConcurrentModificationException看看异常是什么时候抛出的比如用subList做修改看看原List有没有跟着变。踩过这些坑之后你讲出来的经验才是活的。第四提前把自己项目里的集合使用场景准备一个。面试官问到集合时大概率会加一句“你项目中哪里用到了”这时候如果你能说一个真实案例比如用ConcurrentHashMap做本地缓存、用ArrayBlockingQueue做异步削峰你的回答就比单纯背八股高出一个档次。集合这部分面好了面试官对你的评价通常不会低因为它是Java功底最直接的体现之一。我建议你把上面每一个考点都自己验证一遍该写代码的写代码该看源码的看源码。资料背得再熟不如自己动手跑一遍记得深。祝面试顺利。
返回列表