贝壳找房秋招Java笔试复盘:考点、算法与避坑指南
贝壳找房的秋招笔试,我是参加的第二批。如果你也正在投递简历、准备技术笔试,我可以先把结论放在前面:贝壳这套Java工程师笔试题,题型结构非常接近一线互联网公司的常规筛选组合——基础知识选择题加算法编程题,广度大于深度,但足够卡掉一批基础不牢、刷题量不够的候选人。当时笔试群里有人考完反映时间紧、编程题只AC了一道,也有人基础扎实、提前半小时交卷。差距基本不在智商,而在对Java核心概念的熟练度,以及平时刷题的手感。
这篇文章不聊面经八卦,纯粹从技术角度复盘贝壳第二批笔试的考情、考点和做题策略。适合正在准备秋招、春招的Java后端同学,也适合想进房产交易这类重业务、重数据平台的工程师参考。我会把笔试涉及到的Java基础考点、集合与并发原理、算法题常见套路,以及我在实际作答中踩过的坑一条一条拆开讲,尽量做到你看完能直接对着目录去补自己的薄弱点。
1. 2024秋招贝壳笔试整体考情:第二批在考什么
1.1 笔试结构与时间分配
贝壳找房的在线笔试一般通过第三方平台进行,比如牛客网或者赛码网,整体由三大部分组成:单选题、多选题和编程题。第二批的时间点通常在九月下旬到十月初,属于秋招主批次,岗位是Java工程师的话,笔试侧重点会比较偏向后端基础、JVM、并发、Spring框架以及数据结构和算法。整套卷子总时长大概90到120分钟,不同批次可能微调,但大体结构就是这个框架。
选择题部分覆盖的面很广,包括Java语法细节、集合框架源码、并发编程、JVM内存模型、操作系统和网络基础,还有少量数据库知识。编程题一般是2到3道,难度从LeetCode简单到中等不等,偶尔压轴题会到中等偏上。这里有个很重要的时间分配经验:选择题尽量控制在45到50分钟内完成,剩下至少40分钟留给编程题。我见过很多同学死在选择题上——因为多选题一纠结就是十分钟,最后编程题连读题时间都不够。如果你自觉算法比较弱,策略可以调整为选择题快做、凭第一感觉选完就过,把时间让给编程题。
1.2 贝壳笔试背后的筛选逻辑
站在出题方的角度看,贝壳笔试并不追求考倒所有人,它要的是“基础知识扎实 + 代码动手能力强”的候选人。为什么是靠选择题考Java基础?因为海量简历投递下,笔试是成本最低的一轮筛选。单选和多选能快速检验你是否有系统性学习过Java,而不是只靠项目里东拼西凑写CRUD。编程题则检验你能不能独立解决一个明确的小问题,这直接关系到实习期能不能快速上手开发任务。
而且贝壳的业务背景——房源检索、房源推荐、订单交易、合同签约、支付对账——对Java工程师的数据库能力、缓存设计、并发处理能力要求都不低。所以笔试题里常会隐含着这些业务场景的影子。比如考Redis常见问题、考MySQL索引原理、考并发工具类用法,本质都是在为这些真实场景招人。把这些逻辑理清楚,你就不会闷头瞎刷题,而是带着“这道题对应什么业务场景”的意识去复习,效率会高很多。
2. Java基础与集合框架:八股文的重点分布
2.1 高频基础考点:从String到异常体系
贝壳选择题第一部分绕不开Java基础语法。这里的高频考点集中在几个地方:String类的不可变性、字符串常量池、equals与hashCode的约定、包装类的缓存范围、异常体系中的受检异常与非受检异常、泛型擦除、反射的基本使用。这些知识点看起来简单,但贝壳会把它包装成“下列代码输出什么”的形式来考,难度一下就不一样了。
举个例子,String相关的题目常考这种坑:String s1 = "abc"; String s2 = new String("abc");问s1 == s2的结果,以及字符串拼接"a" + "b"和new String("ab")的区别。这背后考察的是JVM常量池和堆内存中对象创建的不同时机。再比如包装类,Integer a = 127; Integer b = 127;比较相等,答案是true,因为Integer缓存默认范围是-128到127;如果把数值换成128,结果就变成false。这种细节在笔试里几乎没有复习死角,只能靠平时积累。
异常体系也爱考。选择题喜欢给一段try-catch-finally代码,问输出顺序和变量值。核心点是:finally中的return会覆盖try中的return,finally中修改基本类型不会影响返回值,但修改引用类型指向的对象会影响。还有Error和Exception的区别,OutOfMemoryError不属于受检异常,程序不应该去捕获它。这些东西在项目里可能一年都用不到一次,但笔试考的就是你基础牢不牢。
2.2 集合框架与并发容器
集合是Java后端笔试的必考模块,也是贝壳这类公司特别爱出多选题的地方。最核心的考点就是HashMap。我建议你至少能手写出HashMap的put流程:先计算key的hash,二次扰动(高16位异或低16位),然后定位到数组桶位;如果桶位为空,直接插入;如果不为空,判断第一个节点是否相同key,是则覆盖;否则判断是不是红黑树节点,按树方式插入;链表就遍历找相同key,找不到就尾插新节点,如果链表长度达到8并且数组长度达到64,就转红黑树。这个流程不光要背下来,还要理解为什么阈值是8、为什么树化还要看数组长度到64。
ArrayList和LinkedList的选择也是一个经典考点。笔试里经常问“频繁增删用哪个、随机访问用哪个”。答案是看情况:ArrayList基于数组,查询快、增删慢;LinkedList基于双向链表,增删快、查询慢。但实际开发里,ArrayList的增删不一定比LinkedList慢,因为数组的批量拷贝和CPU缓存命中率都不差。如果题目出现在多选题里,这种“反直觉”的选项往往是陷阱。
并发容器也是贝壳爱考的。ConcurrentHashMap的JDK 7和JDK 8实现区别、为什么JDK 8要放弃分段锁改用CAS加synchronized、CopyOnWriteArrayList的写时复制机制、BlockingQueueArrayBlockingQueue与LinkedBlockingQueue的区别,这些都要能说出个一二三。我自己的复习经验是画一张对比表,把结构、锁粒度、线程安全性、迭代器弱一致性列出来,考前过一遍非常高效。
2.3 JVM与内存问题
JVM在三类题里都会出现:选择题、多选题,偶尔编程题里也会埋一个内存溢出的坑。贝壳的热搜词里也飘着java: outofmemoryerror: insufficient memory,说明很多同学在实际跑题时都被内存问题卡过。先理清JVM内存区域:堆、虚拟机栈、本地方法栈、程序计数器、方法区(JDK 8之后是元空间)。对象主要分配在堆上,线程私有的栈存放栈帧、局部变量表、操作数栈。方法区的常量池和静态变量在JDK 8之后移到了堆中的字符串常量池和元空间,这里经常出判断题。
OOM的几种类型要能区分:Java堆空间溢出(java.lang.OutOfMemoryError: Java heap space)、元空间溢出、栈溢出(StackOverflowError)、直接内存溢出。选择题里会给你一段代码,问会发生什么异常或者如何解决。解决思路无非是调整堆参数、检查内存泄漏、减少对象的无效引用。如果编程题的评测环境报内存不足,很可能是你的算法复杂度太高,比如用了极端规模的递归、无限添加集合元素,这时候要回头优化代码,而不是干瞪眼。
3. 并发编程与Java新特性考点
3.1 synchronized、volatile与锁机制
并发编程是Java工程师笔试的重头戏,贝壳也不例外。synchronized和volatile的区别属于送分题,但要让分数真正到手,你得把底层机制讲清楚。volatile保证可见性和禁止指令重排,但不保证原子性;synchronized保证原子性、可见性和有序性。为什么volatile不保证原子性?因为像i++这种读改写操作,在并发下多个线程可能同时读到旧值,然后各自加1再写回,导致丢失更新。
synchronized的锁升级过程也是高频考点:无锁态、偏向锁、轻量级锁、重量级锁。JDK 6之后对synchronized做了大量优化,锁只能升级不能降级。一个对象刚开始没有锁竞争,偏向锁会记录线程ID;一旦有别的线程竞争,就升级为轻量级锁,通过CAS自旋获取;自旋超过阈值再升级为重量级锁,依赖底层操作系统的互斥量。选择题里经常问“偏向锁是否可以被其他线程访问”,答案是当其他线程尝试获取时,偏向锁会撤销并升级为轻量级锁,注意是“其他线程可以竞争”而不是“直接访问”。
3.2 线程池与Java 8特性
线程池参数是必背内容。核心线程数、最大线程数、空闲存活时间、时间单位、阻塞队列、线程工厂、拒绝策略,这七个参数在笔试里经常以小计算题的形式出现。比如阻塞队列容量为10,核心线程数为2,最大线程数为5,当前有8个任务同时提交,问最终会创建几个线程、任务放在哪里、有没有触发拒绝策略。正确流程是:先提交给核心线程,核心线程满了放阻塞队列;队列满了才创建新线程到最大线程数;最大也满了才触发拒绝策略。注意是先放队列,不是先扩容线程池,这一点太容易错了。
Java 8的Stream和Lambda也是考点。选择题可能让你预测一段Lambda表达式的输出,或者要求选出正确的函数式接口用法。要熟记四个核心函数式接口:Function、Consumer、Supplier、Predicate。Stream的中间操作是惰性的,只有遇到终止操作才会执行,比如filter().map().collect()只有在collect时才会真正遍历。排序用sorted(),去重用distinct(),分组用groupingBy()。平时写项目的时候多用Stream处理集合,笔试里就能自然反应出运行结果,不然光靠背容易搞混。
4. 算法编程题精讲:笔试题型的实战套路
4.1 从排序题看手撕代码的标准流程
编程题里排序算法出现的频率极高,尤其是快速排序和归并排序。贝壳笔试里有可能直接让你实现快速排序,也可能表面是求TopK或者数组第K大,本质上还是要你会快排思路。我先给一个能直接用的快排模板:
public void quickSort(int[] arr, int left, int right) { if (left >= right) return; int i = left, j = right; int pivot = arr[left]; 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); }注意这里的边界判断:arr[j] >= pivot和arr[i] <= pivot的等于号不能少,否则在存在相同元素时会陷入死循环。取pivot如果取左边界,必须先让右指针j向左移动;反过来取右边界则要先动左指针。这个顺序问题我在笔试考场上栽过一次,后来整理了一个口诀:“基准在左,右先走;基准在右,左先走。”另外面试和笔试里经常追问快排的复杂度,最好顺便记住:平均O(nlogn),最坏O(n²),空间复杂度O(logn)(递归栈)。
4.2 高频题型:字符串、双指针与动态规划
贝壳编程题喜欢考字符串处理,这和它的业务有一点关系——房源搜索、地址解析、用户查询词处理都离不开字符串。常见的题型包括最长无重复字符子串、字符串反转、括号匹配、编辑距离。最长无重复子串可以用滑动窗口加哈希表解决,模板如下:
public int lengthOfLongestSubstring(String s) { Map<Character, Integer> map = new HashMap<>(); int left = 0, maxLen = 0; for (int right = 0; right < s.length(); right++) { char c = s.charAt(right); if (map.containsKey(c)) { left = Math.max(left, map.get(c) + 1); } map.put(c, right); maxLen = Math.max(maxLen, right - left + 1); } return maxLen; }这里有一个重要技巧:当遇到重复字符时,左指针不能简单跳到重复字符的下一个位置,而是要取当前left和新位置的较大值,防止左指针回退。如果你在笔试中AC不了这道题,大概率就是这里没处理好。
动态规划也是压轴题常客。比较典型的有爬楼梯、最长公共子序列、编辑距离、最大子数组和。我建议你一定要把“编辑距离”这道题吃透,因为它既是贝壳等公司的高频题,又能衍生出一堆变种。核心状态转移是:如果word1[i] == word2[j],那么dp[i][j] = dp[i-1][j-1];否则取插入、删除、替换三种操作中的最小值再加1。初始化时要注意第0行和第0列表示空串到某个串的距离。笔试时动态规划题宁可先写一个朴素递归加记忆化,也不要不写,因为记忆化往往能拿大部分分数。
4.3 输入输出与自测技巧
在线笔试里,输入输出格式搞错导致0分的案例非常多。贝壳使用的是标准输入输出,需要自己写Scanner或BufferedReader。如果输入包含大量数据,推荐用BufferedReader加StringTokenizer来读,比Scanner快很多。例如:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = br.readLine(); int n = Integer.parseInt(line); StringTokenizer st = new StringTokenizer(br.readLine()); while (st.hasMoreTokens()) { int a = Integer.parseInt(st.nextToken()); }编程题提交后如果报错,优先检查三类问题:数组是否越界、是否有未处理的大数溢出(比如用int存超出20亿的中间结果)、下标从0开始还是从1开始。考场上不要写了代码直接交,先自己在本地多造几个测试用例,尤其是边界用例,比如空数组、单元素数组、全是重复元素的数组、数据量最大的情况。我自己的习惯是每题AC后不急着做下一题,先花1分钟检查输入范围,再决定要不要改用long。这道工序在贝壳笔试的评测环境里救过我好几次。
5. 框架、数据库与业务场景题
5.1 Spring与Spring Boot重点
贝壳技术栈以Spring Boot为主,笔试题对框架的考察虽然不是开放性简答,但选择题里会涉及不少Spring核心概念。IoC和AOP是送分题,Bean的作用域、生命周期也经常出现。Bean的生命周期大致是:实例化、属性填充、初始化(InitializingBean、init-method)、使用、销毁。Spring Boot的自动配置原理也是高频题,核心是@EnableAutoConfiguration加载META-INF/spring.factories中的配置类,再配合@ConditionalOnClass等条件注解按需加载。笔试里一般不要求你背出完整链路的类名,但要能选出“自动配置基于什么机制实现”的正确答案。
Spring事务管理也很重要。事务的传播行为——REQUIRED、REQUIRES_NEW、NESTED——在项目里经常用到,选择题会用嵌套调用的场景来考。有一个经典陷阱:同一个类内部方法调用,事务注解会失效,因为Spring事务基于AOP代理,内部调用不会走代理。如果你在贝壳业务系统里写代码,订单、支付这种对一致性要求极高的模块,这个知识点不仅能帮你答题,还能让你在后续面试的简历项目描述里更出彩。
5.2 MySQL与Redis高频考点
数据库题在Java笔试里跟并发一样稳居前三。MySQL部分最常考的是索引、事务隔离级别和SQL优化。索引要理解B+树的优势:矮胖、适合范围查询,叶子节点有双向指针,回表减少。最左前缀法则必须会:复合索引(a,b,c)能用到a、a,b、a,b,c,但跳过a直接用b就失效。事务隔离级别有读未提交、读已提交、可重复读、串行化,MySQL默认是可重复读。MVCC的原理理解到“隐藏字段+undo log+ReadView”这个程度就够了。SQL优化题则会给你一条慢查询,让你分析为什么慢、怎么改,思路一般围绕是否走索引、是否覆盖索引、是否使用了函数导致索引失效、能否改写SQL或加索引。
Redis在笔试里的出现频率也很高。常考的点包括:Redis为什么快(纯内存、单线程、I/O多路复用)、数据类型和适用场景、持久化方式RDB与AOF的区别、缓存穿透、缓存击穿、缓存雪崩的区别和解决方案。这里有个容易混淆的点:缓存穿透是指查询一个根本不存在的数据,绕过缓存直接打到了数据库;缓存击穿是指某个热点key过期瞬间,大量请求同时打到数据库;缓存雪崩是指大量key同时过期,导致数据库压力骤增。解决方案分别是布隆过滤器或缓存空值、互斥锁或逻辑过期、过期时间加随机值或做多级缓存。这三兄弟是笔试和面试的常客,一定要分清。
5.3 结合贝壳业务的系统设计思路
贝壳笔试的编程题或附加题偶尔会带有业务场景的味道,比如房源搜索、房源推荐、最近带看记录、订单状态流转。这类题本质上还是算法题,但要注意业务对边界条件的约束。举个例子,给一批房源坐标和用户位置,让你找出最近的K套房源,这类题解法是维护一个大小为K的大顶堆,或者直接用快速选择算法。如果数据量大到内存装不下,还可以考虑分治或GeoHash分区,不过在笔试环境里,写出堆排解法就够了。
系统设计虽然不是笔试主菜,但选择题里可能夹杂一些场景判断。比如“房源详情页访问量高,应该如何优化”,选项里有页面静态化、CDN缓存、Redis缓存、异步消息队列。你要能根据题目描述的热点程度和数据一致性要求做选择。我做这类题的原则是:先读一致性要求,再看可用性要求,最后看吞吐量。这个思路在秋招笔试里普适性很强,不局限于贝壳一家。
6. 笔试环境与常见问题排查
6.1 环境配置坑位:JDK版本、编码与Lombok
贝壳笔试的编程环境一般已经预置好了JDK版本,但你在本地用IDE敲代码时,很可能会遇到一批来自本机环境的问题。热搜词里飘着源发行版 17 需要目标发行版 17,这个问题十有八九是JDK版本和IDE编译级别不匹配导致的。如果你本机装的是JDK 17,但IDE的Project Structure里设置的编译级别是11或者8,编译就会报这个错。解决方法是去IDE里把Project SDK和Java Compiler的Target bytecode version统一,或者直接切换到项目要求的JDK版本。
另一个高频报错是Lombok相关的:You aren't using a compiler supported by lombok。这通常是因为当前JDK版本太高,而Lombok版本太旧,不兼容新版本的Java编译器。我在考场上看到这个报错时,第一反应就是去检查pom.xml或build.gradle里的Lombok版本,升级到1.18.30以上就能匹配JDK 17。如果你在笔试平台提交代码,遇到这个报错基本可以肯定是环境问题,不会影响你核心代码的逻辑正确性,但你需要快速解决才能测试,所以本地电脑还是提前把Lombok版本调好比较安心。
乱码问题也值得一提。在VSCode里跑Java,控制台中文输出一堆乱码,可能是编码格式不匹配。Windows下默认GBK,而VSCode默认UTF-8,改法有两种:在launch.json中加上"console": "internalConsole",或者设置JVM参数-Dfile.encoding=UTF-8。笔试阶段不要小看这个问题,如果编程题的输入包含中文字符串(比如地址处理题),编码不对时整个答案都会错。
6.2 做题时的常见失误与检查清单
我把笔试里最容易踩的坑整理成了一份速查表,考前一小时过一遍比刷十道新题都管用:
- 数组越界:循环边界是
< length还是<= length,下标从0开始还是从1开始。 - 大数溢出:题目数值范围超过2^31-1时,用long而不是int,或者用BigInteger。
- 空指针:处理字符串、数组、Map时先判空,尤其是编程题的异常用例。
- 死循环:指针移动条件里必须保证指针单调移动,快排和双指针问题容易出现。
- 多组输入:有的题目要求读取多组测试数据,直到EOF,要会写
while (in.hasNext())或while ((line = br.readLine()) != null)。 - 变量覆盖:同一个作用域里不要重复定义同名变量,编译不报错但逻辑可能完全不是你想的那样。
我曾在笔试里遇到一个题目,给出的输入规模很大,我用了O(n²)的双重循环,提交直接超时。后来改成双指针O(n)才过。这个教训很简单:提交前先看数据范围,如果n在10的5次方以上,就不要再想双重循环了,考虑O(nlogn)的排序、二分,或者O(n)的哈希表、双指针。贝壳的评测标准比较严格,超时就是0分,就算思路对也没用。
7. 备考路线与实用资源参考
7.1 三个月备考时间线
如果你距离笔试还有三个月,我建议按“基础巩固—专项突破—真题模考”三个阶段来安排。第一个月专注Java核心知识点,把集合、并发、JVM三个模块的源码和原理理清楚,同时保持每天2到3道LeetCode简单题的热身。第二个月开始专项训练,每天上午看八股文专题,下午集中刷LeetCode热点题目,按字符串、双指针、动态规划、二叉树、图论这样的标签来练,刷到每个标签15题以上。第三个月进入模考状态,每周选2到3天按照贝壳笔试的真实时长做套卷,用牛客网的历年真题或模拟试卷。
时间少的话也有穷办法:如果只有两周,优先保证HashMap原理、JVM内存模型、并发三大特性、MySQL索引、Redis缓存穿透这几个核心考点,算法题只刷高频Top 30,尤其是有手写题模板的排序、二叉树遍历、滑动窗口、单调栈这四类。注意,编程题一定不能只看不写,笔试考场上键盘敲得利索的人,全都是平时手撕代码练出来的。
7.2 值得反复刷的资料与正确用法
网上Java面试的资料非常多,但我不建议贪多。我自己复习时核心用的是JavaGuide(Java基础、集合、并发、JVM的梳理很全)、LeetCode Hot 100(覆盖面试高频题型)和牛客网的历年真题。JavaGuide适合当字典查,碰到不懂的知识点去翻对应章节,不要从头到尾读,那样效率太低。LeetCode要按标签刷,不要按题号顺序刷。牛客网的真题卷一定要做,因为它的界面和出题风格跟实际笔试环境高度一致,提前适应能减少考场的紧张感。
还有一个小技巧:每学完一个知识点,用自己的话把它讲出来,或者写到博客里。这样做的好处是逼你整理成有逻辑的语言,而不是零散的记忆碎片。我当年整理过一份HashMap和ConcurrentHashMap的对比笔记,半个月后笔试真的考到了相关内容,考场上下笔非常顺。知识梳理这件事,投入产出比很高。
7.3 笔试之外的隐性加分项
笔试只是秋招的一环,通过后紧接着就是面试。贝壳这类公司非常看重候选人是否能快速落地业务,所以简历里如果有和房产交易、搜索、推荐、订单系统相关的项目经验,会很加分。即使没有,也要准备好一个能讲清楚“技术难点—解决思路—最终效果”的项目,可以是一个秒杀系统、一个短链接服务、一个分布式爬虫,或者一个自己写的IM系统。
笔试过程中如果编程题有一道没有AC,也不要太慌。我笔试结束后跟几个拿到面试通知的同学交流过,贝壳不是单纯按AC数量筛人,选择题正确率和代码规范度也会影响综合评分。所以即使AC两道,只要选择题不崩,还是有机会进面试。反过来,如果AC了三道但选择题错一堆,可能也过不了。这提醒我们:基础题的分数一定要守住,算法题尽力冲就行了。
