拓冰建站拓冰建站
首页 / 资讯中心 / 正文

小鹏汽车Java笔试题解析:从集合并发到JVM与算法

2020年小鹏汽车的秋招Java笔试题放到今天来看依然很有参考价值。原因很简单小鹏这类造车新势力的Java岗位考的不是传统车企那套嵌入式、总线协议的东西而是标准互联网后端那一套——Java基础、集合、并发、JVM、Spring、MySQL、算法覆盖面广但深度适中。如果你正在准备车企的Java后端岗笔试这篇文章值得花十几分钟读完。我会把当年笔试的题型结构、高频考点、编程题思路和踩坑点都拆开讲争取让你看完能直接照着准备。1. 笔试整体设计小鹏Java岗到底在考什么1.1 2020年这场笔试的题型分布先说说整场笔试的大致结构。当时是线上笔试全程摄像头监控总时长90分钟题目分为客观题和编程题两个大块。我根据当时多方收集的反馈和个人答题印象整理了一份典型的题型分布模块题型题量建议用时分值占比Java基础单选多选20题左右25分钟30%集合与并发单选多选10题左右10分钟15%JVM与异常单选多选8题左右8分钟12%数据库与框架单选多选10题左右12分钟15%编程题手写代码2题25分钟28%注意一个细节多选是少选不得分、错选不得分所以做多选题的时候宁可少选也别乱选。这是很多第一次参加线上笔试的人容易忽略的我见过不少人在多选题上栽跟头一题1.5分错三道就相当于一道编程题白写了。时间分配上客观题最好控制在55分钟内留出25到30分钟给编程题。编程题题量虽然只有两道但其中一道往往考察的是边界条件的处理不是单纯写出来就完事的。如果前面客观题拖太久后面编程题很容易因为时间紧张漏掉关键分支。1.2 车企Java岗的考察倾向小鹏作为造车新势力Java后端主要做两块一块是车联网平台负责车辆数据上报、远程控制、OTA升级等业务另一块是用户运营和商城系统跟在电商公司做后端差别不大。这两个业务场景决定了笔试的考察倾向并发处理、数据一致性、接口性能这些互联网通用能力是重点同时也会带一点车联网场景题。和其他互联网大厂比小鹏的笔试题目广度足够大但深度不会特别变态。比如HashMap原理一定会考但不至于追问到红黑树的变色和旋转细节并发必考但不会让你手写一个带状态机的工作流引擎。整体感觉是筛选扎实的基础功而不是找算法竞赛选手。所以准备方向很明确把Java核心基础吃透把HOT算法题刷熟远比钻牛角尖研究冷门源码有意义。另外要注意一个趋势现在的车企秋招笔试场景题比例在逐年增加。2020年那会儿主要是纯理论题但后面几届开始出现类似车辆轨迹数据百万级并发写入怎么设计这类偏架构的题目。如果基础题都搞定了建议提前看看常见的系统设计思路别等技术面再临时补。2. Java核心考点逐题拆解2.1 面向对象与语言特性选择题里的送分题和陷阱题笔试第一部分上来就是Java基础主要考面向对象三大特性、重载重写、接口和抽象类的区别还有JDK8开始普及的lambda和枚举。这些属于送分题但送分题里也有陷阱。比如有一道题很典型问下面方法哪些是正确重载overload。给几个选项其中有个选项是把返回值类型从void改成int但方法名和参数列表完全相同。很多人看到方法名一样就选了实际上这不算重载Java只认方法签名方法名参数列表返回值类型不参与签名判断。类似的问题还有重写override时访问修饰符能不能变小答案是不能子类重写方法的访问权限不能低于父类。接口和抽象类这道老生常谈的题也出现了不过问法换成了JDK8之后接口有了默认方法那抽象类还有什么存在的意义。这个问题的核心是接口的默认方法虽然有实现了但不能持有状态实例字段而抽象类可以。所以当你要给多个子类提供公共的成员变量时抽象类依然是不可替代的。如果只背了接口多继承、抽象类单继承这种结论遇到这种变体题就容易卡壳。lambda和函数式接口也是高频考点。笔试里出现了一道题给定一个ListString要求用lambda表达式过滤掉空字符串并转成大写然后收集成新的List。考察的是stream().filter().map().collect()这条常用链路。这里有个细节容易被忽略collect(Collectors.toList())返回的是ArrayList如果你需要不可变集合得用toUnmodifiableList()或者collectingAndThen包一层。面试官很多时候不直接考这个但会在你跑完代码后追问一句。枚举也是常客。题目大概是枚举能不能定义构造方法和抽象方法答案是都可以。很多新手以为枚举就是一组常量实际上Java枚举是全功能的类可以有构造器、字段、方法甚至可以按枚举类型实现抽象方法。下面这个例子是笔试题的一个简化版public enum Status { PENDING(0, 待处理), PROCESSING(1, 处理中), SUCCESS(2, 成功), FAILED(3, 失败); private final int code; private final String desc; Status(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } public String getDesc() { return desc; } public static Status fromCode(int code) { for (Status s : Status.values()) { if (s.code code) { return s; } } throw new IllegalArgumentException(unknown code: code); } }这道题其实在考一个工程意识状态流转如果用int魔法值来维护散落在代码里很容易出错用枚举统一管理配合fromCode工厂方法类型安全又方便扩展。笔试里这种题不算难但能区分出你是会写Java还是只会用Java。2.2 集合框架HashMap源码级考察是必考题集合框架这一块2020年小鹏的笔试题基本押在HashMap和ArrayList上尤其是HashMap。可以说只要笔试里出现HashMap的put过程相关题目后面大概率还有关于扩容、红黑树转换、负载因子的追问。虽然笔试是客观题居多但下面的源码级细节是必须掌握的。先看JDK8的put流程先通过hash(key)计算扰动后的哈希值这里的扰动函数就是h key.hashCode() ^ (h 16)目的是让高位参与计算减少冲突。然后(n - 1) hash算出数组下标。如果该位置为空直接插入如果不为空判断链表头节点是否key相同相同则覆盖否则遍历链表找到key就覆盖找不到就尾插。当链表长度达到8且数组长度达到64时才转成红黑树。这里有两个条件缺一不可——如果数组长度没到64优先扩容而不是转树。负载因子0.75和扩容也是考点。默认容量16当size 16 * 0.75即12个元素时触发扩容每次扩容为原来的两倍需要rehash重新分配位置。这里有个典型的错误说法扩容就是简单把数组翻倍其实JDK8针对旧链表节点做了优化节点要么留在原下标要么移动到原下标旧数组长度的位置利用的正是hash oldCap这一位是否为0来判断。这个优化让扩容效率大幅提升面试中能说出来是极大的加分项。再来看ArrayList它考的是扩容机制add时如果elementData满了调用grow方法新容量是旧容量的1.5倍即oldCapacity (oldCapacity 1)。如果还不够就直接扩展到所需容量。很多人记不住1.5倍这个细节没关系但你至少要知道ArrayList初始容量是10扩容是动态增长的。还有fail-fast机制。笔试有一道题问为什么在foreach循环中删除元素会抛ConcurrentModificationException而用Iterator.remove()不会。答案是foreach语法糖在编译后使用了Iterator但在每次next()时都会检查modCount是否等于expectedModCountArrayList的remove方法会改变modCount导致两个值不一致从而抛异常。而Iterator.remove()在删除后会把新的modCount同步给expectedModCount所以不会抛。这个细节虽然简单但很能体现阅读源码的习惯。2.3 并发与线程池车企平台真的很吃并发小鹏的车联网平台每天要接收海量车辆上报的数据并发场景非常突出。笔试里考察并发是必然的而且集中在synchronized和ReentrantLock的区别、volatile的作用、线程池参数含义这几个点上。synchronized和ReentrantLock的区别标准回答包括synchronized是关键字自动加锁解锁ReentrantLock是API需要手动lock和unlock通常配finally释放。ReentrantLock支持可中断获取锁、超时获取锁、公平锁还能同时创建多个Condition队列这些都是synchronized不具备的。JDK6之后synchronized经过锁升级偏向锁→轻量级锁→重量级锁性能已不输ReentrantLock所以性能有差距这种结论已经是过时的说法了。volatile也是高频考点。它保证可见性和有序性但不保证原子性。笔试中常见陷阱是多个线程对volatile变量做i最终结果会不会等于1000答案是不会因为i是读改写三步操作。这个问题如果再往前问一步就是如何保证原子性答案是使用AtomicInteger的CAS或者加锁。线程池这块几乎每次笔试都会给出一段配置ThreadPoolExecutor executor new ThreadPoolExecutor( 2, // corePoolSize 核心线程数 5, // maximumPoolSize 最大线程数 60, TimeUnit.SECONDS, // 空闲线程存活时间 new LinkedBlockingQueue(10), // 工作队列 Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() );考点分三层。第一层是参数含义核心线程数、最大线程数、队列容量分别是什么。第二层是执行流程提交任务时如果当前线程数小于核心线程数创建核心线程执行如果大于等于核心线程数优先放入队列如果队列满了再尝试创建非核心线程如果线程数已达到最大线程数队列也满了才执行拒绝策略。很多人会把队列满了才创建非核心线程理解成先创建线程再进队列这是一个非常容易被笔试选择题抓住的误区。第三层是拒绝策略。AbortPolicy直接抛异常CallerRunsPolicy让提交任务的线程自己执行DiscardPolicy静默丢弃DiscardOldestPolicy丢弃队首任务。笔试一般考前两个。另外还要知道Executors工厂方法的隐患newFixedThreadPool用的是无界LinkedBlockingQueue任务堆积可能导致OOMnewCachedThreadPool最大线程数是Integer.MAX_VALUE线程过多也可能OOM。所以大厂笔试题里经常强调禁止使用Executors创建线程池小鹏这里也会用类似的引导。2.4 JVM内存与异常别只会背八股JVM这块考的是运行时数据区、类加载、垃圾回收、以及OOM的排查思路。2020年的卷子里有一道题直接给了这么一段报错日志java.lang.OutOfMemoryError: insufficient memory问可能的原因和处理思路。这个报错看起来简单但实际藏了几个点。insufficient memory出现时先判断是堆内存不足还是堆外内存不足。如果是堆内存不足常见错误是Java heap space而insufficient memory往往是JVM获取不到本地内存native memory导致可能是创建线程过多、DirectByteBuffer申请过多、或者其他进程占满了机器内存。排查步骤一般是先用jstat -gcutil看堆使用率再用jmap -heap看堆配置接着用jstack看线程数量和状态最后用jcmd或者Native Memory Tracking分析堆外内存占用。运行时数据区也是必考关键要区分线程共享和线程私有的部分。堆和方法区JDK8之后是元空间是线程共享的虚拟机栈、本地方法栈、程序计数器是线程私有的。这里经常出现一个判断错误有人把元空间说成是堆的一部分。JDK8之前方法区也叫永久代它和堆是并列的JDK8之后永久代被元空间取代使用的是本地内存更不是堆的一部分。垃圾回收算法可以按标记-清除、标记-复制、标记-整理三个来答但笔试更爱考可达性分析和GC Roots。GC Roots包括栈帧中的局部变量、静态变量、JNI引用等。有一道选择题说那些对象可以作为GC Roots选项里有被一个匿名内部类引用的对象这个不能选因为匿名内部类引用的是实例对象只有被GC Roots直接或间接引用的对象才会被视为存活而不是任何对象都能当Root。异常体系也是Java基础里常考的点。笔试出现了这样一道题Error和Exception有什么本质区别检查异常和非检查异常的区别。Error是JVM级别的严重问题比如StackOverflowError、OutOfMemoryError程序不应该也不大可能捕获处理Exception是程序层面的问题可以捕获处理。检查异常checked exception强制要求try-catch或throws比如IOException非检查异常runtime exception不强制处理比如NullPointerException、ArrayIndexOutOfBoundsException。热词里有个java中数组越界异常说的就是ArrayIndexOutOfBoundsException它属于IndexOutOfBoundsException子类而后者又是RuntimeException的子类。3. 编程题实操手写代码的得分点3.1 手写冒泡排序和快速排序别在这种题上翻车编程题第一道往往是排序2020年卷子里冒泡排序和快速排序都出现了。排序题虽然基础但很多人翻车不是不会写而是边界控制不好、代码不简洁或者没注意到优化点。冒泡排序常规写法是两层循环但笔试里如果能写出一版带是否交换标志的优化版本印象分会明显高一些public void bubbleSort(int[] nums) { if (nums null || nums.length 2) { return; } boolean swapped; for (int i 0; i nums.length - 1; i) { swapped false; for (int j 0; j nums.length - 1 - i; j) { if (nums[j] nums[j 1]) { int tmp nums[j]; nums[j] nums[j 1]; nums[j 1] tmp; swapped true; } } if (!swapped) { break; } } }优化点在于如果某一趟没有任何交换说明序列已经有序可以直接结束。最坏情况和平均情况时间复杂度都是O(n^2)最好情况已有序优化后可以降到O(n)空间复杂度O(1)。写的时候还要注意数组为null和空数组的防御性判断这类代码虽然简单但代码整洁程度也是评分项。快速排序也是高频中的高频。笔试要求手写快排时我建议用挖坑法逻辑直观不容易出错public void quickSort(int[] nums, int left, int right) { if (left right) { return; } int pivot nums[left]; int i left, j right; while (i j) { while (i j nums[j] pivot) { j--; } if (i j) { nums[i] nums[j]; } while (i j nums[i] pivot) { i; } if (i j) { nums[j--] nums[i]; } } nums[i] pivot; quickSort(nums, left, i - 1); quickSort(nums, i 1, right); }平均时间复杂度O(nlogn)最坏情况下数组已经有序且每次选到最左元素作为pivot退化成O(n^2)。如果笔试要求高一点可以提一句随机选择pivot可以避免最坏情况这是加分项。但注意nums[j] pivot和nums[i] pivot的等号不能丢否则遇到相同元素时会陷入死循环。3.2 链表操作反转链表和环形链表检测第二道编程题往往在链表、二叉树、动态规划里面选。反转链表在2020年的笔试题里出现过而且是不带头结点的单链表反转。这个题有两种解法迭代和递归笔试里首选迭代因为递归容易爆栈。public ListNode reverseList(ListNode head) { ListNode prev null; ListNode curr head; while (curr ! null) { ListNode next curr.next; curr.next prev; prev curr; curr next; } return prev; }核心思路就是三指针保存当前节点的下一个节点然后把当前节点的next指向前一个节点随后三个指针集体后移。写的时候最容易错的是忘记在修改curr.next之前保存next节点导致链表断掉。我见过很多人在白板编程时栽在这个小地方后面代码全乱。如果笔试还有余力可以准备下环形链表检测快慢指针。方法是定义slow和fast两个指针slow每次走一步fast每次走两步如果能相遇说明有环。这类题的边界条件也比较典型空链表、单节点链表、头尾成环的链表最好都在草稿纸上推一遍。3.3 动态规划入门爬楼梯和最大子数组和动态规划在2020年小鹏的笔试题里不算难考的是最经典的爬楼梯问题每次可以爬1阶或2阶爬到第n阶有多少种不同方式。状态转移方程很简单dp[i] dp[i-1] dp[i-2]初始值dp[1]1dp[2]2。如果直接用数组空间复杂度是O(n)。但笔试里如果能进一步优化成滚动数组空间复杂度压到O(1)会非常亮眼public int climbStairs(int n) { if (n 2) { return n; } int prev2 1; int prev1 2; for (int i 3; i n; i) { int cur prev1 prev2; prev2 prev1; prev1 cur; } return prev1; }这个题本质上是斐波那契数列的变体所以改写成递归虽然能跑但会超时笔试时注意别踩坑。如果要更进阶可以提一下矩阵快速幂可以做到O(logn)但一般不用写到代码里。还有一道常见的动态规划题是最大子数组和要求在一个整数数组里找到一个连续子数组使得子数组元素和最大。核心思路是dp[i] max(nums[i], dp[i-1] nums[i])最终答案是所有dp[i]的最大值。这个题跟爬楼梯一样属于入门级动态规划建议练到不用思考就能写出来的程度。3.4 编程题的答题技巧编程题除了答案正确平台判题还看代码效率和边界处理。在线的笔试系统一般不会要求你写出完整类定义核心方法写对就行但方法签名要和题目要求一致参数名、返回值类型不能错。一个很实用的建议如果某道题没有完全做出来先把思路用注释写在代码里比如尝试用双指针先排序再对撞。有些笔试系统会保留你的代码作为面试官参考即使没有通过全部测试用例展示了分析过程也能救回一部分分数。另外遇到输入特别大的测试用例时要学会反推复杂度的瓶颈。如果n是10^5级别O(n^2)的解法基本会超时此时要考虑排序、双指针、哈希表或二分这些O(nlogn)甚至O(n)的方案。4. 数据库与框架问答4.1 SQL编写与索引设计笔试里的拉分项数据库部分基本围绕MySQL展开题型有两种一道手写SQL几道关于索引和事务的选择题。手写SQL很常规考的是查询每门科目成绩都大于80分的学生信息这类问题。这题的经典做法是用NOT EXISTS逻辑很清晰SELECT DISTINCT s.student_id, s.student_name FROM student s WHERE NOT EXISTS ( SELECT 1 FROM score sc WHERE sc.student_id s.student_id AND sc.score 80 );也可以这样写先找出存在任一科目成绩小于等于80分的学生然后从总表中排除掉这些学生。实际笔试时只要SQL能跑通结果正确一般都给分。但如果你能在答案里同时用GROUP BY和HAVING写一版会更像一个有经验的工程师SELECT student_id, student_name FROM student WHERE student_id IN ( SELECT student_id FROM score GROUP BY student_id HAVING MIN(score) 80 );索引设计的考点集中在哪些情况下索引会失效。典型场景包括对索引列使用函数比如WHERE YEAR(create_time) 2020、隐式类型转换比如索引列是varchar但查询条件是数字、LIKE以通配符开头LIKE %abc、联合索引不满足最左前缀原则。这些点几乎每场笔试都要考背熟了就是稳拿分。还有一个高频概念是explain的执行计划。笔试不会真的让你跑SQL但会问type字段的取值级别从好到差依次是system、const、eq_ref、ref、range、index、all。all代表全表扫描是优化的大忌。能答出这个顺序说明真的看过执行计划不是只会背概念。4.2 Spring与Spring Boot框架题的高频姿势框架部分小鹏考察Spring框架和Spring Boot基本用法不会特别深入源码但核心思想必须清晰。最重要的两个概念是IOC和AOP笔试一般问IOC解决了什么问题AOP在哪里用过。IOC的本质是控制反转把对象的创建和依赖管理交给容器好处是解耦方便扩展和测试。AOP是面向切面编程经典应用是事务管理、日志输出、权限校验。如果能在答题时举一个具体例子比如用Transactional注解实现数据库事务的自动提交与回滚会让答案更立体。Spring Bean的生命周期也是一道高频题特别是单例模式在这里怎么体现。Bean的默认作用域是singleton整个容器只创建一个实例这和GoF单例模式的区别在于Spring的单例是容器级别的并不限制你只能用getBean获取。笔试中还出现过Autowired和Resource的区别注意这个细节Autowired是Spring提供默认按类型注入Resource是JSR-250规范默认按名称注入找不到再按类型。Spring Boot的考点集中在自动配置上。题目会问SpringBootApplication注解包含哪几个注解。答案是SpringBootConfiguration、EnableAutoConfiguration、ComponentScan。其中EnableAutoConfiguration通过Import(AutoConfigurationImportSelector.class)读取META-INF/spring.factories文件里的配置类再按条件注解ConditionalOnClass、ConditionalOnMissingBean决定是否加载。笔试一般只需要答出自动配置通过条件注解按需加载这个层面但如果能说出spring.factories这个机制会让面试官觉得你确实看过源码。另外热词里出现了java springboot apikey 安全对接这个点虽然笔试不一定直接考但我建议在准备阶段了解下。Spring Boot做接口鉴权时常见方案是自定义Filter或Interceptor校验API Key配合请求签名和时间戳防重放。这类工程经验在笔试问答题中如果正好命中会是很好的加分内容。4.3 数据库与框架题的实战建议笔试做数据库和框架题时我发现很多人容易犯一个毛病SQL题不写分号或者只写核心查询条件不写表名。在线笔试平台是按测试用例判分的SQL语句不仅要求语法正确还要求字段名和表名完全匹配。所以写SQL时先看清楚题目给的表结构把字段名、表名原样抄过来别自己改名。框架题如果问Spring Boot项目启动时Bean初始化顺序尽量画出阶段流程来帮助记忆扫描Component → 读取配置类 → 实例化Bean → 依赖注入 → 初始化前(BeforeInitialization) → 初始化(InitializingBean/PostConstruct) → 初始化后(AfterInitialization) → 单例Bean就绪。这个顺序是面试追问的常客笔试虽然不用画图但能按顺序答出来正确率会高很多。5. 常见失分点与备考建议实录5.1 时间分配翻车多选浪费了我太多时间说实话我第一次做这套题时最大的问题是时间管理。客观题里多选题非常耗时因为不确定的选项反复琢磨一道多选能卡上5分钟。结果做到编程题时只剩15分钟第一题排序勉强写完第二题链表反转没时间调试直接心态崩了。复盘下来正确策略应该是单选和填空题尽量不纠结会就选不会就标记后跳过多选题控制在每题1到1.5分钟超过时间先凭第一感觉选上回头有时间再检查。编程题优先做自己擅长的、思路明确的题目保证一题全过好过两题都半吊子。在线笔试系统通常每道编程题有多个测试用例部分通过也有分所以要先把最简单的用例跑通再逐步优化。还有一个细节是代码编译环境。一些笔试系统默认Java版本可能比较老如果用到var、List.of()这些高版本语法编译直接失败。所以笔试前先确认系统支持哪个Java版本尽量用JDK8语法写代码避免无谓的编译错误。热词里有一条源发行版 17 需要目标发行版 17的报错也是类似的问题平时用IDE开发时不容易遇到一旦放到在线平台就很容易踩坑。5.2 这些知识不复习必丢分根据我对多场车企Java笔试的观察有五个知识点是必考但经常被忽略的列个清单帮助大家自查第一HashMap在JDK7和JDK8的实现差异。JDK7用头插法、会成环JDK8用尾插法、引入红黑树。这道题几乎每场面试都会考一定要能比较清楚地说出来。第二线程池执行流程。先判断核心线程数、再入队、再判断最大线程数、最后走拒绝策略这个顺序不能混。第三JVM垃圾回收算法和可达性分析。CMS和G1的区别要能说两句G1是区域化、可预测停顿CMS是标记-清除、并发收集易产生碎片。第四MySQL索引的最左前缀原则和explain的type级别。这个能区分你是不是真的做过SQL优化。第五Spring的代理机制。AOP默认用的是JDK动态代理还是CGLIB答案是如果目标类实现了接口Spring默认用JDK动态代理如果没有实现接口则使用CGLIB。Spring Boot 2.x之后默认开启了CGLIB代理用来解决某些JDK代理场景下代理对象类型不一致的问题。如果时间有限优先把这五个点刷透基本能覆盖笔试50%以上考点的正确率。剩下的精力再投到算法题和框架源码上。5.3 三轮复习法从基础题到模拟实战最后分享一个我后来总结的备考节奏分三轮走效率会比直接刷题高很多。第一轮是撒网式复习把Java基础、集合、并发、JVM、Spring、MySQL的知识点全部过一遍不需要太深但要确保每个考点都有概念看到题目能反应出考察方向。第二轮是针对性刷题挑高频题做专项练习比如HashMap的实现原理、线程池参数、快排手写、SQL索引优化做到手熟。第三轮是全真模拟用在线笔试系统限时90分钟做整套题训练节奏和心态。我个人的感受是第三轮模拟往往是最容易被忽略但最有效的一环。很多知识你平时看着都会一到限时环境就各种手误测试用例挂掉。只有通过模拟才能发现自己真正的时间瓶颈在哪、编程题的调试方式顺不顺手。最后再分享一个小技巧笔试开始前先把草稿纸和IDE准备好遇到编程题先在草稿纸上写清伪代码、标出边界条件再往代码框里填。这样既能减少错误也能让你在紧张状态下保持清晰的思路。希望这份复盘对你有用祝顺利拿到心仪的offer。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门