十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

货拉拉Java笔试题全解析:从字符串常量池到线程池的考点梳理

货拉拉Java笔试题全解析:从字符串常量池到线程池的考点梳理 2018年秋招期间我完整刷过这套货拉拉Java工程师笔试题卷三B老实说这套题在我刷过的几十套Java笔试题里不算最难但考察面非常典型几乎每一道题都踩在Java开发日常工作的核心点上。时隔几年回头看这套题里的知识点依然能在现在的面试中出现比如字符串常量池、HashMap扩容、线程池参数、JVM内存模型、Spring事务传播行为全是大厂小厂都在问的基础题。这篇文章我不打算逐题贴标准答案而是按这套卷子的考点模块把每一类题背后真正想考察的能力、常见的错解、以及一线开发里对应的实战场景拆开讲一遍。无论你是正在准备校招、社招还是干了几年Java想回头补基础这篇内容应该都能让你少走一些弯路。1. 试卷整体印象与考点布局1.1 卷面结构与出题逻辑这套卷子的结构和当时大多数互联网公司校招Java岗的笔试题基本一致整体分为三大块选择题、简答题、编程题。从货拉拉当时的技术栈来看业务以同城货运的订单调度、司机管理、路线规划为主后端大量使用Java技术体系笔试题的出题思路明显偏向“基础扎实度工程实践能力”的双重考核。选择题大概覆盖了Java基础语法、集合框架、JVM、并发编程、异常处理这几大方向。简答题侧重Spring框架的理解、数据库索引优化、以及一些常见场景设计题。编程题通常是一道算法题加一道简单的逻辑实现题难度中等偏下不会刻意出特别偏的算法但很考验编码规范性和边界条件处理能力。值得一提的是这套卷子里的题目虽然看起来每题都是独立知识点但出题人其实在暗中串联一条线从“对象在内存里怎么存”到“集合在扩容时发生了什么”再到“多线程访问共享变量时怎么保证可见性”顺着这条线把Java最核心的运行时机制串起来了。如果你在刷题时只看单个题目而不去建立知识网络碰到变形题就很吃亏。1.2 考点权重与核心分布我根据记忆把这个卷子的考点分布整理了一下方便大家对照复习。考点模块题目占比核心考察内容难度评估Java基础语法约25%字符串、包装类、运算符、异常偏基础集合框架约20%HashMap原理、ArrayList扩容、集合安全中等JVM与内存约15%内存区域、GC机制、类加载中等偏难并发编程约15%锁机制、线程池、volatile、CAS较难Spring框架约10%IOC、AOP、事务传播行为中等数据库约10%索引、SQL优化、事务隔离级别中等算法与逻辑约5%排序、字符串处理、简单数据结构中等从这张表能看出Java基础语法和集合框架的占比最大这也是校招笔试的一贯风格。原因很简单这两块最能在短时间内筛选出“Java功底是否扎实”的候选人。集合框架里的HashMap几乎是必考题而JVM和并发编程是区分度高、最能拉开差距的部分。我自己的体会是刷这套题不能只背结论比如“HashMap默认容量16负载因子0.75”这种背诵式复习做选择题还行一旦简答题追问“为什么负载因子是0.75而不是1”卡壳的人非常多。这也是我想写这篇解析的原因——把题背后的逻辑讲透比记住答案重要得多。2. Java基础选择题详解字符串、集合与运算符2.1 字符串相关的那些经典陷阱这套卷子的选择题里字符串相关的题目几乎必出而且每次都会有人错。最常见的出题方式是给你一段代码让你判断创建了几个对象或者某个字符串拼接的结果是否相等。这里有一个大家特别容易踩的坑字符串常量池与堆内存的区分。先看一道我记得的题目变体String s1 new String(hello); String s2 hello; String s3 s1.intern(); System.out.println(s1 s2); System.out.println(s2 s3); System.out.println(s1 s3);第一个比较结果是falses1是通过new创建的对象存储在堆上s2指向字符串常量池中的对象两者引用地址不同。第二个比较结果是trues3调用intern方法后如果池中已有“hello”就返回池中的引用s2本身就在池里所以相等。第三个是falses1始终是堆里的独立对象。这个知识点在面试里还会被拓展成 String、StringBuilder、StringBuffer 三者的区别String不可变每次拼接都生成新对象适合少量字符串操作StringBuilder可变线程不安全但性能最高StringBuffer可变且线程安全方法加了synchronized性能稍逊。如果在循环里做大量字符串拼接正确做法是使用StringBuilder这个结论我在实际开发里验证过无数次。2.2 集合Framework的必考细节货拉拉这套卷子的集合部分HashMap的出镜率最高。题目通常集中在几个核心点上put操作的完整流程、扩容机制、为什么线程不安全、JDK 1.7和1.8的差别。要理解HashMap建议从一次put操作到底发生了什么开始把整个链路理顺这样无论题目怎么换都能应对。put一个键值对时首先通过hash方法计算key的哈希值这个hash方法并不是直接使用key.hashCode()而是把hashCode的高16位与低16位做异或运算目的是让高位信息也能参与定位减少哈希冲突。然后根据数组长度减一n-1做与运算得到数组下标。如果该位置为空直接存入新节点如果不为空遍历链表或红黑树如果key存在则覆盖value否则新增节点。当链表长度超过8且数组长度大于64时链表会树化为红黑树。扩容是另一个高频考点。HashMap默认初始容量16负载因子0.75当size超过阈值capacity * loadFactor时触发扩容。扩容后容量翻倍但注意并不是简单地把旧数组复制到新数组而是每个节点都要重新计算位置这也是JDK 1.7中并发扩容形成循环链表的根本原因。JDK 1.8改用尾插法配合红黑树优化解决了死循环问题但put操作在并发下仍然可能丢失数据所以高并发场景还是要用ConcurrentHashMap。2.3 运算符与包装类的隐藏考点运算符相关题目看着简单但错误率不低。最常见的坑是“整数除法”和“自增运算符”。比如 int a 5; int b a a; 这种题目很多人算不准。a是先用后加a是先加后用所以b的运算过程是5加7等于12运算结束后a的值是7。这种题在实际开发中遇到的机会不多但笔试里就是用来筛掉基础不牢的人。包装类的考点就更贴近实战了。Integer缓存范围是-128到127在这个范围内用valueOf返回的是缓存对象超过范围则new新对象。所以 Integer a 127; Integer b 127; a b 结果为true而128时结果为false。这个机制叫享元模式的应用实战中如果做了大量的Integer对象创建合理利用缓存能减少内存消耗。但题目往往不会直接问缓存机制而是隐藏在数值比较的代码里一不注意就做错了。还有一点值得提醒基本类型和包装类型混合运算时包装类型会自动拆箱如果拆箱对象是null直接抛 NullPointerException。这类问题在线上真实发生过比如从Map里get一个不存在的key返回null然后直接与int比较瞬间整条链路都挂了。看这套笔试题时要养成一种敏感度看到包装类型和基本类型运算立刻想到空指针风险。3. JVM内存与对象生命周期从笔试题到线上问题排查3.1 运行时数据区域划分JVM部分是这套卷子里拉开差距的地方。选择题和简答题都会涉及到选择题偏重概念判断简答题偏重理解描述。绝大部分人在这部分失分的原因不是不懂而是答得太碎没有体系感。JVM运行时数据区划分为线程共享和线程私有两大类。线程私有的有程序计数器、虚拟机栈、本地方法栈线程共享的有堆和方法区在JDK 8中方法区被元空间取代。虚拟机栈里的栈帧包含局部变量表、操作数栈、动态链接、方法出口每个方法调用对应一个栈帧入栈出栈。堆里存放的是几乎所有的对象实例而对象的类型信息存放在元空间。这里有一个高频问法一个对象从创建到销毁经历了哪些内存区域完整答案是new对象时在堆分配内存对象头标记分代年龄局部变量引用存在栈帧的局部变量表类型信息从元空间加载GC时对象根据存活情况在新生代和老年代移动。3.2 对象存活判断与垃圾回收算法判断对象是否存活是GC的基础。引用计数法虽然简单但无法解决循环引用问题所以JVM使用可达性分析算法。从GC Roots出发沿着引用链遍历能被遍历到的对象存活不能被遍历到的对象被标记为回收。GC Roots包括虚拟机栈引用的对象、方法区静态属性引用的对象、JNI引用的对象等。垃圾回收算法里有三个基本功标记-清除、标记-复制、标记-整理。标记-清除会产生内存碎片适合老年代标记-复制不会产生碎片但浪费一半空间适合新生代因为新生代对象绝大多数朝生夕灭标记-整理适合老年代没有碎片但移动对象有额外开销。这套知识点之所以重要是因为你在配置JVM参数时需要理解每个参数的底层原理否则就是调参玄学。这个考点让我想起一个真实的线上事故。有一次我们服务出现频繁Full GC每次Full GC停顿好几秒接口超时率飙升。排查到最后发现是某段代码在循环里拼命创建大对象几百兆的对象全被塞进老年代。老年代空间不足触发Full GC结果回收效果不好内存碎片化严重。后来通过调整新生代和老年代的比例配合G1垃圾回收器的参数优化才把问题稳定下来。笔试虽然只考概念但概念理解不透彻排查线上问题就像盲人摸象。4. 并发编程与线程池高频考点也是高频失分点4.1 synchronized与Lock的底层视角并发编程在这套卷子里的占比不是最高但每道题都值得花时间深挖。synchronized是必考对象题目通常从锁的升级过程入手。JDK 1.6对synchronized做了大量优化引入了偏向锁、轻量级锁、重量级锁的升级机制。锁最初是偏向锁只有一个线程访问时不需要真正加锁一旦有第二个线程竞争升级为轻量级锁通过CAS自旋获取锁自旋失败且竞争加剧时升级为重量级锁线程进入阻塞状态。这里有一个常见追问synchronized和ReentrantLock怎么选我的建议是能用synchronized就用synchronized因为虚拟机会持续优化它而ReentrantLock的优势在于更灵活的锁操作可中断、可超时、可实现公平锁、可以绑定多个Condition条件队列。但要注意ReentrantLock必须手动释放锁容易忘写finally块而synchronized是自动释放的出错率更低。4.2 volatile、CAS与可见性volatile关键字也是这套卷子的常客核心考点是内存可见性和禁止指令重排。但很多人没搞清楚volatile能保证可见性但不能保证原子性。经典例子是多个线程同时对volatile变量执行count最终结果一定小于预期值因为count本质是“读-改-写”三步三步之间可能被其他线程打断。CAS比较并交换是并发包的基础原语AtomicInteger等原子类底层就是用CAS实现的。CAS有三个操作数内存地址V、期望值A、新值B只有当V中的值等于A时才把V更新为B否则什么都不做。CAS解决了原子性问题但引入了ABA问题一个线程把值从A改成B又改回A另一个线程CAS比较时发现值还是A就误以为没被修改过。解决方案是加版本号Java里的AtomicStampedReference就是这么做的。这套题里如果出到原子类相关题目ABA问题是一个隐藏极深的考察点。4.3 线程池参数与任务执行流程线程池几乎是所有Java笔试题的“保留节目”货拉拉这套卷子也不例外。最核心的就是ThreadPoolExecutor的七个参数核心线程数、最大线程数、空闲存活时间、时间单位、阻塞队列、线程工厂、拒绝策略。要理解线程池必须知道任务提交后的完整执行流程。流程是这样的核心线程数未满时直接创建核心线程执行任务核心线程数已满任务进入阻塞队列队列已满开始创建非核心线程执行任务最大线程数已满且队列已满触发拒绝策略。这个流程我在实际工作中发现能答对的人不超过一半很多人以为只要队列满了就创建新线程忽略了队列是先于非核心线程的。拒绝策略有四种AbortPolicy直接抛异常、CallerRunsPolicy调用者线程执行、DiscardPolicy直接丢弃、DiscardOldestPolicy丢弃队列最老的任务。生产环境我推荐使用CallerRunsPolicy它能在系统过载时把压力反馈给调用方自然限流不容易雪崩。还有一个容易忽略的问题创建线程池时务必自定义线程工厂给线程起一个可辨识的名字否则出问题排查起来异常痛苦。5. Spring与数据库简答题的重头戏5.1 Spring IoC与AOP的考察深度Spring相关的简答题在某种程度上区分了“背过八股文”和“真正写过项目”的人。IoC控制反转的核心是把对象的创建和管理交给容器由容器负责依赖注入。面试官常问“IoC带来了什么好处”标准答案无非是解耦、统一管理、方便扩展。但实际工作里IoC最大的价值体现在测试的时候因为依赖是通过接口注入的Mock起来非常方便。AOP面向切面编程的考点更细包括切点表达式、通知类型、动态代理方式。Spring AOP默认使用JDK动态代理要求目标类实现接口如果没有接口则使用CGLIB生成子类代理。这里有坑CGLIB代理通过继承实现所以final类和方法无法被代理如果想用AOP切final方法会直接失效。事务注解Transactional其实也是AOP的一种应用默认只对RuntimeException回滚对受检异常不回滚这个点很多老手都会忽略简答题如果把事务和AOP结合起来考考察的就是综合理解而不是死记硬背。5.2 事务传播行为与隔离级别数据库事务相关的题目通常出现在简答题。Spring事务传播行为有七种最常考的是REQUIRED和REQUIRES_NEW。REQUIRED是有事务就用当前事务没有就新建REQUIRES_NEW是无论如何都新开事务。关键场景是外层方法有事务内层方法用REQUIRES_NEW时内层事务提交或回滚不影响外层反之亦然。这个机制在记录日志、同步数据等场景中很有用比如主业务失败时仍希望把日志记录下来就要用REQUIRES_NEW。事务隔离级别有四个读未提交、读已提交、可重复读、串行化。MySQL默认是可重复读但很多大厂因为主从同步和日志解析的需求会改成读已提交。隔离级别解决的问题对照表是必背内容读未提交可能脏读读已提交解决脏读但可能不可重复读可重复读解决不可重复读但可能幻读串行化全部解决但性能最差。MySQL的InnoDB在可重复读级别下通过间隙锁和MVCC解决了幻读问题这个点如果能在简答题中写出来会给面试官留下很好的印象。5.3 索引优化与SQL执行计划数据库索引相关的题目在这套卷子里也占一定比例通常考察索引失效的场景和SQL优化思路。索引失效的典型场景包括对索引列使用函数、隐式类型转换、like以百分号开头、联合索引违反最左前缀原则、使用or且一侧没有索引。面试官问这些不是考记忆而是考你排查慢SQL的思路。实际排查慢SQL时第一步是打开慢查询日志找到超过阈值的SQL第二步用EXPLAIN查看执行计划重点看type字段。type从好到差依次是system、const、eq_ref、ref、range、index、ALL如果看到ALL说明全表扫描必须想办法加索引优化。extra字段里出现Using filesort或者Using temporary也说明有优化空间。我实操中经常遇到的情况是单表数据量不大时索引效果不明显一旦数据量过千万索引设计的好坏直接决定接口是200毫秒还是5秒。6. 算法编程题从冒泡到快排的手写基本功6.1 排序算法的手写与边界处理编程题部分通常有专门的排序题这让我想起热搜词里频繁出现“冒泡排序java”和“快速排序java实现”说明这确实是大家搜索最多、最常考的内容。货拉拉这套卷子的算法题难度不高但考察一个很重要的能力能否在限定时间内写出无bug的代码。以快速排序为例手写快排是非常好的代码能力试金石。快排的思想是分治选一个基准值把数组分成小于基准和大于基准两部分然后递归处理。写法上有一个容易出错的点分区函数中while循环的边界处理。这里给一个我常用的模板比较稳妥public 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 temp arr[i]; arr[i] arr[j]; arr[j] temp; } } arr[left] arr[i]; arr[i] pivot; quickSort(arr, left, i - 1); quickSort(arr, i 1, right); }注意两个内层循环里必须加上 i j 的判断否则会出现下标越界。这个细节我见过很多候选人现场写的时候丢掉导致程序异常。快排的时间复杂度平均O(n log n)最坏O(n²)最坏情况是数组已经有序时每次选的基准都是最小或最大值。优化方式是三数取中法从首、中、尾三个位置取中间值作为基准。6.2 字符串与数组的常见算法变形除了排序编程题通常还会有一道字符串或数组处理的题目。常见的有字符串反转、判断回文、最长公共前缀、数组去重、两数之和、链表反转。两数之和是最经典的HashMap应用题目标值减当前值的差值如果在Map里存在直接返回对应下标否则把当前值放入Map。时间复杂度O(n)空间换时间的典型思路。链表相关题目虽然不一定会出现但链表反转值得多练因为这是很多复杂链表题的基础。链表反转的迭代写法是用三个指针prev、cur、next依次反转指向public ListNode reverseList(ListNode head) { ListNode prev null; ListNode cur head; while (cur ! null) { ListNode next cur.next; cur.next prev; prev cur; cur next; } return prev; }写链表题最重要的就是别丢失引用反转前必须先把next保存起来否则一旦断开就无法继续遍历了。这种代码细节在笔试里非常加分因为大多数人的算法思路没问题但代码实现漏洞百出。6.3 编程题答题技巧与时间分配编程题不只是考察算法本身还考察你的工程素养。我的建议是遵循四个步骤第一先明确输入输出格式确认边界条件第二严格审题思考是否存在特殊情况比如数组为空、只有一个元素、重复元素第三写出算法思路再动手编码不要边想边写容易乱第四写完代码后用一两个简单样例走一遍在脑内模拟执行检查逻辑是否有遗漏。时间分配上建议给编程题留足30分钟以上。先把所有题目通读一遍按熟悉程度排序先做容易拿分的题把最难的放到最后。如果一道题卡了15分钟还没有清晰思路先跳到下一题不要在单题上死磕。笔试的通过率往往取决于你会做的题有没有做对而不是不会做的题有没有突破。7. 笔试踩坑复盘与备战建议7.1 高频失分点汇总我汇总了做这套题以及后来面试中观察到的几个高频失分点写成自查清单供大家对照集合类线程安全判断不清HashMap线程不安全Hashtable线程安全但性能差ConcurrentHashMap才是正确选择。字符串比较误用String的内容比较必须用equals比较的是引用地址。自动拆箱引发空指针包装类型为null时参与运算直接NPE。try-catch-finally执行顺序判断失误finally里的return会覆盖try里的return但finally里修改基本类型值不会影响返回值。HashMap容量初始化不重视有大量数据插入时未指定初始容量频繁扩容浪费性能。线程池饱和处理策略选择不当生产环境用AbortPolicy会让业务直接抛异常要用CallerRunsPolicy限流。Spring事务失效场景不清楚同类内部调用、私有方法、抛出受检异常都会导致事务回滚失效。MySQL索引优化忽略最左前缀原则联合索引建了但查询条件没按顺序来索引直接失效。快排等手写算法边界条件漏判数组为空、单元素等特殊情况没处理程序异常退出。7.2 基于这套题的复习路线建议如果你正在准备Java后端岗位的笔试我的建议是不要只刷题而是按知识体系走一轮系统复习再通过刷题检验查漏。复习的主线可以按照“Java基础→集合→JVM→并发→Spring→数据库→算法”的顺序来这和货拉拉这套卷子的考点权重是匹配的。Java基础方面重点掌握String相关机制、包装类缓存、异常处理机制、Object类方法、equals与hashCode的约定。集合方面除了HashMapArrayList的扩容机制也是常客默认容量10扩容时1.5倍增长每次扩容都要数组拷贝所以预判元素数量后主动指定初始容量是很好的优化习惯。JVM方面运行时数据区、GC算法、类加载双亲委派是三大核心。并发方面synchronized锁升级、volatile语义、线程池参数、CAS原理缺一不可。Spring方面IoC和AOP概念、Bean生命周期、事务传播行为是底线。数据库方面索引原理B树、事务隔离级别、SQL优化、Explain执行计划是重点。算法刷题方面按类型做整理比按题号刷更高效。数组类、字符串类、链表类、二叉树类各集中刷二三十道题每个类型的常见套路就基本掌握了。排序算法手写是基本功快排、归并、堆排最好都能默写出来。7.3 我个人在复盘这套题后的一些体会这套题解答完毕之后我最大的感受是Java笔试的题目再怎么变底层考察的始终是“你是否真正理解Java程序的运行机制”。背答案可以应付一部分题但遇到灵活的简答题和变形选择题没有深入理解就只能靠猜了。另一个体会是笔试题目和实际开发的关系是“同源不同层”。比如HashMap的扩容机制在笔试里是一段文字但在线上就是一次性能瓶颈的根因线程池参数在笔试题里是几个数字但在生产环境就是决定服务稳定性的关键配置。备考时如果有意识地把题目往“真实场景”上靠理解效率会高很多。如果你也在准备Java方向的笔试这套货拉拉的题值得拿来当自测题做一遍做完对答案之后务必把错题涉及的知识点展开学习而不是只记住本题的答案。祝各位都能收获理想的offer。
返回列表