尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

2015搜狗Java笔试题复盘:基础、并发、JVM与排序算法全解析

2015搜狗Java笔试题复盘:基础、并发、JVM与排序算法全解析 前几天整理硬盘翻出一份2015年搜狗JAVA工程师笔试题。说实话看到这张卷子的瞬间我脑子里立刻浮现出当年笔试时的那张草稿纸——写得密密麻麻最后一道手写排序题还没写完就被收卷了。这份题不算特别难但覆盖面极广从运算符到JVM都有涉及属于典型的“看着都会一写就错”的类型。今天这篇文章我就把这张卷子从头到尾拆一遍说说每题背后的考察意图也聊聊哪些地方最容易被坑。这篇文章适合三类人看正在准备JAVA面试、刷八股文背考点的人想了解搜索类公司技术岗位到底怎么挑人的求职者以及哪怕已经工作几年想回头补一补基础的同学。我不打算把题念一遍就完事而是以复盘视角把每道题相关的原理、失分点、后续延伸都讲透。毕竟笔试不是目的通过笔试进入面试、拿到Offer才是。1. 2015年的搜狗笔试为什么值得反复看1.1 这份卷子背后的行业坐标先交代一下时间背景。2015年的Java生态跟今天差别很大Java 8刚发布一年多Lambda和Stream还属于“新特性面试题”大多数线上项目还跑在Java 7甚至Java 6上Spring Boot处于早期普及阶段很多人还在写XML配置的SSM容器、微服务、云原生这些词没有今天那么泛滥。搜索公司像搜狗当时的业务主要靠搜索、输入法和浏览器这三块后端大量使用Java对工程师的要求非常直白基础要硬并发要懂内存要敏感还要能处理搜索场景里那种动不动几十万、几百万级别的字符串数据和索引问题。这种背景下笔试题必然不会只考“背得出API”而是把考点藏进小陷阱里考察你有没有真正写过代码、遇到并解决过问题。所以你会在这张卷子里看到很多“看起来很基础一选就错”的题目这不是出题人无聊而是有意为之。另外当时移动互联网还在高速增长搜狗这类公司对后端工程师的需求量很大但筛选标准并不低笔试就是第一道门槛。1.2 出题偏好的三个“信号”结合卷子整体结构和后来搜狗面试的常见流程可以提炼出三个信号第一偏重Java语言基础的深度。从运算符到集合框架再到并发和JVM覆盖得非常均匀几乎没有纯框架题。能感觉到出题人想考察的是“语言本身”而不是“你用过哪个框架”。第二算法题量不大但手写代码题必考。常出现的就是数组、字符串、排序这类经典题难度多在Easy到Medium之间重点看代码规范和边界处理。第三会有一两道带业务色彩的开放题比如和搜索、匹配、去重相关的场景设计。这类题不是让你背答案而是看你的工程思维。换句话说这份笔试题的筛选逻辑是先筛掉基础不牢的人再筛掉只会背题的人最后通过开放题筛掉没有实战经验的人。这三个信号放到现在面试同样适用所以我一直觉得2015年的卷子并不“过时”。相反它能帮你快速定位自己的知识短板。2. 基础题最容易拿分也最容易踩坑2.1 运算符与表达式送分题里的“地雷”基础部分通常是一批选择题很多看起来人畜无害实际上全是陷阱。我印象最深的一类是自增运算int a 5; a a; // a 的值是多少正确答案是5不是6。原因在于a先返回旧值5再进行自增最后赋值操作又把旧值5写回了a。这个知识点在笔试里出现频率极高考的就是你有没有真正理解“表达式的返回值”和“变量的最终值”是两回事。很多人在实际开发里很少写这种代码但一旦笔试遇到就会凭感觉选6。要避免丢分最好的办法是在本地IDE里跑一遍观察字节码理解i和i在操作数栈里的行为。还有一个经典陷阱是和的自动类型转换short s 1; s s 1; // 编译报错s1是int s 1; // 编译通过隐式强转回short是复合赋值运算符自带一次隐式强转所以不会编译报错。很多人第一次遇到这个题会懵但只要你理解Java的“小类型转大类型是自动的大类型转小类型需要显式强转”这条规则就不容易错。类似的还有x x 1和x 1在x为byte/short时的区别几乎年年都有公司考。switch也能考出花来。Java 7之后才支持String所以2015年考“switch能接收哪些类型”的答案就包含了byte、short、int、char、枚举和String。这里容易错的是switch不支持long、float、double为什么因为switch的底层是lookupswitch和tableswitch指令这两条指令只支持int类型的比较char、byte、short、枚举和String都会映射成int。搞清楚这个原理你就不用死记硬背“支持哪些类型”了。三目运算符的类型提升也是个高频坑。比如Object result true ? 1 : 2.0;result装箱后是Double类型不是Integer。因为条件表达式里1和2.0比较时会被提升为更高精度的double。这类题不写代码很难发现所以我的建议是考前把这些小点全部自己写一遍代码验证别只背结论。2.2 面向对象继承、重载、抽象类与接口面向对象是Java笔试题的“基本盘”可以说每一份卷子都有搜狗这份也不例外。常考的切入点有三个**第一个是重载与重写。**重载看的是参数列表只看方法名和参数类型、个数、顺序返回类型不参与重载判断所以“两个方法只有返回类型不同算不算重载”的答案是不算。重写要求方法签名一致访问权限不能更严抛出的异常不能更宽。有一个很容易忽略的细节重写时如果父类方法抛出Checked Exception子类方法可以不抛但不能抛出新的或更宽的异常。这个点很多背题的人会掉进去因为教材里只写“不能抛出更大的异常”没有强调“可以不抛”。**第二个是构造函数的执行顺序。**子类构造前会先走父类构造器父类构造器又会先初始化自己的成员变量和初始化块。经典题目是class Parent { static { System.out.print(P-static ); } { System.out.print(P-instance ); } Parent() { System.out.print(P-ctor ); } } class Child extends Parent { static { System.out.print(C-static ); } { System.out.print(C-instance ); } Child() { System.out.print(C-ctor ); } }执行顺序是父类静态块、子类静态块、父类实例块、父类构造器、子类实例块、子类构造器。这里的关键是静态初始化只在类加载时执行一次实例初始化则每次new都会执行。笔试里很爱考这个顺序建议直接背下来再配合自己写代码打印一遍印象最深。**第三个是抽象类与接口的选择。**2015年考的主要是“抽象类可以有构造器、可以有非抽象方法接口只能是抽象方法JDK8之前”。放到今天接口有了default方法和静态方法这个题的答案已经变了但出题逻辑没变考的是你对“抽象概念”和“实现细节”之间边界的理解。实际工程里我一般遵循一个原则能用接口描述能力、契约的优先用接口需要复用状态和代码逻辑时才考虑抽象类。2.3 集合框架HashMap、ArrayList 和 HashSet集合框架在笔试题里占的比例相当大尤其是HashMap和ArrayList几乎是必考。HashMap2015年那会儿JDK8刚出来很多人还在用JDK7的思路答题这就成了区分点。核心考点有这么几个底层结构JDK7是数组链表JDK8是数组链表红黑树。什么时候链表转红黑树链表长度达到8且数组长度达到64。为什么引入红黑树解决哈希冲突严重时链表查询退化成O(n)的问题转树后能降到O(logn)。为什么线程不安全JDK7中并发put可能导致环形链表扩容时出现死循环JDK8中则可能出现数据覆盖问题因为put操作不是原子的。这个考点特别值得展开。很多人背了“JDK8为什么线程不安全”的答案但不知道具体场景。JDK8的PUT流程里如果两个线程同时发现某个桶位为空并同时执行casTabAt后写入的数据就会覆盖先写入的这就是数据丢失。面试官如果追问“那ConcurrentHashMap怎么解决”你就得能说出CAS synchronized锁桶头节点的设计。ArrayList的扩容机制也是一道经典题。初始容量是10每次扩容为原来的1.5倍int newCapacity oldCapacity (oldCapacity 1);底层是Arrays.copyOf也就是新建数组、System.arraycopy迁移元素。这种题的坑在于很多人只记得“扩容1.5倍”但不知道add(int index, E element)和addAll方法中c.toArray返回的是Object[]在极端情况下可能抛ClassCastExceptionJDK8之前有个著名的坑。HashSet的底层其实是HashMapvalue是一个固定的Object占位key用来存储元素。所以HashSet的遍历顺序不保证稳定这和HashMap的哈希存储天然相关。考这个点题目往往不会直接问“HashSet底层是什么”而是拐弯问“HashSet为什么不能存重复元素”本质就是HashMap的key不可重复你把这个逻辑讲透分数就到手了。基础部分先说到这里。这一整块拿不拿得稳基本决定了你能不能进下一轮。因为基础题如果丢分多后面进阶题做得再好也很难补回来。3. 进阶核心区并发、JVM、算法拉分关键3.1 多线程与并发必考而且层层递进并发题在2015年的搜狗卷子里占有明确的权重。搜索公司后端每天要处理海量请求并发意识不是加分项而是生存技能。常考的点按难度可以排成一条线第一个是synchronized和ReentrantLock的区别。常规答案是ReentrantLock支持公平锁、可中断、可超时、支持多个条件变量synchronized是JVM层面的关键字使用简单JDK6之后引入了偏向锁、轻量级锁性能已经不比Lock差多少。笔试里真正容易丢分的是“可重入”这个点——synchronized和ReentrantLock都支持可重入但很多人会以为只有Lock支持这是典型的背漏了。第二个是volatile到底解决了什么。很多人说“volatile保证线程可见性”这只是其一它还能禁止指令重排序但不保证原子性。所以volatile int count做count仍然是线程不安全的因为count是“读-改-写”三步不是原子操作。这里我见过很多面试者用volatile去解决计数问题结果在高并发下数据对不上这就是对语义理解不到位。第三个是ThreadLocal的内存泄漏问题。ThreadLocal是每个线程一个副本底层是ThreadLocalMapkey是ThreadLocal的弱引用value是强引用。坑在于如果线程一直存活并且ThreadLocal对象被回收value就永远无法被访问到但强引用还留在Map中造成泄漏。所以规范用法是用完必须remove()。这个点在笔试中常以“ThreadLocal为什么会导致内存泄漏”出现懂原理才能答到位。第四个是线程池参数。corePoolSize、maximumPoolSize、keepAliveTime、workQueue、RejectedExecutionHandler这五个参数的含义和协作顺序是高频题。关键是要讲清楚当请求到来时线程池是先创建核心线程还是先丢队列还是直接拒绝。流程是当运行线程数小于corePoolSize新建线程大于corePoolSize时优先丢进队列队列满了创建非核心线程直到maximumPoolSize再满就触发拒绝策略。很多人会把“先新建线程”和“先入队列”记反丢分很可惜。提示面试官特别喜欢在这个点上连环追问。比如“如果corePoolSize设置成10maximumPoolSize设置成20队列容量100现在同时来了30个请求会发生什么”你要能流利答出前10个创建核心线程后20个进队列没有非核心线程创建。只有队列满了才会创建非核心线程。这种推导能力比背参数重要得多。3.2 JVM内存与溢出别让 OutOfMemoryError 吓住你相关热词里挂着“java: outofmemoryerror: insufficient memory”说明JVM内存题是很多人的共同痛点。笔试常见考点分两块第一块是运行时数据区。堆Heap、虚拟机栈VM Stack、本地方法栈Native Method Stack、方法区Method Area、程序计数器PC Register。要能说出每个区域的职责以及哪些区域会抛出OutOfMemoryError运行时区域作用异常示例堆存放对象实例java.lang.OutOfMemoryError: Java heap space虚拟机栈描述Java方法执行的内存模型StackOverflowError也可能OOM本地方法栈为Native方法服务Native方法栈空间不足时OOM方法区/元空间存放类信息、常量、静态变量JDK8之前PermGen spaceJDK8之后Metaspace程序计数器当前线程执行字节码的行号指示器唯一不会OOM的区域这个表格基本能应对“哪些区域会OOM”这种题。不过我想多提醒一句笔试考JVM不只是考区域划分更多是考“你遇到OOM时怎么排查”。比如Java heap space大概率是对象太多或大对象太多这时候要dump堆栈分析Metaspace则可能是动态生成类太多比如反射、CGLIB代理使用不当。如果能说出这个差异说明你不是死记硬背。第二块是GC的基本模型。面试官常问对象什么时候进入老年代答案包括大对象直接进入老年代、长期存活的对象在经历一定次数Minor GC后晋升、动态年龄判断等等。笔试题则可能给出一个场景问存活对象被移到了哪个区域。这类题不用去背所有细节先把“新生代Eden区、Survivor区、老年代”的流转路线画出来再配合几个参数比如-Xmx、-Xms、-XX:MaxPermSize理解就能应对大多数问题了。关于“insufficient memory”这个具体报错我在实际运维中见过很多次它跟Java heap space不是一个概念。它通常是操作系统层面无法再分配本机内存常见原因是进程的虚拟内存耗尽、机器物理内存不足或者容器内存限制。排查思路是先看系统可用内存再看JVM堆设置是否合理然后看是否有线程数爆炸导致每个线程占用独立栈空间过大最后考虑是不是Native内存泄漏。笔试里如果出现这种题考的不是你会不会调参而是你能不能快速定位“堆内还是堆外”的方向这个思路比具体命令更重要。3.3 算法与数据结构数组、指针和手写排序算法题在2015年搜狗笔试里的占比没有BAT那么变态但一定会有一两道手写代码。高频类型就是数组、字符串和排序这也是热词里“数组和指针笔试题”“冒泡排序java”“快速排序java实现”扎堆出现的原因。Java里没有指针这个概念但“数组和指针”这类题在C/C试卷里很常见Java版则一般改成“数组引用传递”或者“数组下标操作”的考题。核心要理解Java对象和数组作为参数传递时传递的是引用值方法内部修改数组内容会直接影响原数组但如果重新给形参赋值不会影响外部实参。这个特性和“指针”问题有异曲同工之处很容易被拿来出选择题。排序算法则是手写代码的经典。冒泡排序是最容易写的但也是复杂度最差的O(n^2)。快速排序是高频中的高频必须能闭眼写出来。我建议先写最简洁的递归版本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--; nums[i] nums[j]; while (i j nums[i] pivot) i; nums[j] nums[i]; } nums[i] pivot; quickSort(nums, left, i - 1); quickSort(nums, i 1, right); }这个写法叫挖坑法好记也不容易错。笔试评分时考官会重点看两点一是边界条件有没有处理比如leftright二是有没有死循环比如等于pivot的元素没处理导致无限交换。写完后自己拿一组含重复元素的数组过一遍就是考场内最好的自测方式。排序算法的复杂度对比也是选择题常客这里放一张常考表排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序O(n²)O(n²)O(1)稳定快速排序O(n log n)O(n²)O(log n)不稳定归并排序O(n log n)O(n log n)O(n)稳定堆排序O(n log n)O(n log n)O(1)不稳定除了排序二分查找、链表反转、字符串去重都是常客。这些题目没有太多花活关键是一个“练”字。我在线下模拟面试时经常说手写代码题不要追求一次写对但要有自测意识写完立刻用两三个用例走查包括空数组、单元素、全部相等这三种边界。能做好这一步就算代码有瑕疵考官也会给你不错的印象分。4. 开放题与手写编程题搜狗气质的“弦外之音”4.1 贴近业务的场景题字符串、匹配和搜索搜狗是做搜索、输入法和浏览器的笔试题里出现和字符串、匹配相关的题并不意外。这类题表面上是考算法实际上是想看你对真实业务问题的建模能力。最常见的套路是给一段文本统计出现次数最多的前K个词。这题实际就是“词频统计TopK”。最简单的方案是用HashMap统计词频然后用小顶堆维护TopK复杂度O(n log k)。更进阶一点如果你知道MapReduce的思想可以说“分片统计后归并”。这题能拉开差距的地方在于会不会讨论内存放不下的情况、会不会提到用字符串哈希减少存储、会不会考虑分词规则对结果的影响。还有一类题和输入法相关比如“如何快速判断一个字符串是否有多个候选拼音/候选词”。这种题一般不要求你直接写完整代码而是给思路。回答时重点要讲清楚数据结构和取舍。比如用前缀树Trie来组织词库每个节点存储一个字符路径代表一个词这样前缀匹配的时间复杂度只跟待匹配字符串长度相关和词库规模无关。如果再配合一个哈希表做精确匹配就兼顾了查找速度和内存占用。这里有个亲身经历可以说一下。我当年遇到一道题大致意思是“给定一个字符串数组找出所有可以由其他单词拼接而成的最长单词”。我一开始只想到暴力两两拼接判断时间复杂度很高。后来才想到可以先按长度排序再用前缀匹配和递归去判断。这种题在笔试中出现不是要你写出最完美的答案而是看你在有限时间内能不能找到一个合理方案并且把思路清晰写出来。4.2 手写代码题的评分潜规则说到手写代码很多同学关心“到底什么程度算过”。我复盘过不少笔试流程也和朋友交流过各家公司的评分习惯大致可以归结为三个档位能跑通逻辑正确边界基本覆盖。这是及格线。写得好命名清晰、思路明确、有注释时间复杂度讲得清楚。这是加分项。有工程感写完会主动说“这个解法在数据量大的时候有什么问题”并且会给出优化方向。这是最亮眼的表现。笔试虽然只是书面代码但阅卷人能从字里行间看出你是“背过答案”还是“真写过”。比如快速排序的递归深度问题如果你写完主动提一句“递归版本在最坏情况下会栈溢出可以用非递归的栈实现”这就是工程感。2015年考这类题时大部分人都是“写出来就完事”能在卷子上多写一句优化思路的人非常少谁写了谁就稳进面试。另外很多人在手写代码时容易犯一个毛病变量名随意。比如用a、b、c或者list1、list2。这在阅卷人眼里等于“没做过工程”。写代码题变量名要能表达业务含义比如pivot、left、right、maxFreq一眼就能看出意图。这一点听起来很虚但在纸笔答题时非常加分。4.3 开放设计题的答题套路最后一种题型是开放设计题比如“设计一个站内搜索提示功能”或者“实现一个关键词过滤系统”。这种题没有标准答案但有一套很实用的答题套路我总结为三步第一步先问需求边界。比如“关键词量级是多少更新频率多高是精确匹配还是前缀匹配对延迟的要求是多少”这一步不是废话而是展示你面对模糊需求时的专业素养。很多同学拿到题就开始写方案结果方向错了后面全白费。第二步给出方案并说明取舍。可以画一个简单的流程输入词→分词/预处理→查索引→返回候选。数据结构选Trie还是哈希表存储层选内存还是DB缓存怎么设计都要讲清楚为什么。比如Trie适合前缀匹配但内存占用高哈希表适合精确匹配但无法做前缀联想。用哈希表做精确匹配加速用Trie做前缀候选两者配合就是一个比较完整的方案。第三步把方案落地到可执行步骤。比如“第一步先用HashMap做精确匹配量级上来之后引入Trie再用LRU缓存缓解热点”。这种从简到繁、逐步演化的思路比直接抛一个大而全的方案更符合真实开发节奏。我当时特别喜欢在最后补充一句“这个方案在单机场景可以支撑多少QPS如果要扩展可以用分片的方式加机器”。这句话能让面试官觉得你真的在思考工程问题而不是在背系统设计题模板。5. 考后复盘那些分数之外的经验5.1 做题顺序与时间分配综合这种卷子的题目分布我的建议是做题顺序上先扫一眼全卷然后把基础选择题控制在30分钟以内进阶题和手写代码题留足45分钟最后15分钟检查。具体来说选择题碰到不确定的先标记不要死磕。笔试时间一分一秒都很宝贵一道运算符题卡5分钟后面的大题就危险了。一个很实际的方法是拿不准的题先凭第一感选上卷子上打个问号等所有题做完再回头推。因为很多考点之间是相通的你做到后面可能会突然想起前面的某个点。手写代码题不管会不会都要写点东西上去。空白卷等于直接放弃写一个错误的思路也可能给你挣到部分分。我记得当年有同学快速排序没写出来但写了基础的双层循环排序最后也拿到了一些代码分。阅卷人更看重的是“你有没有在思考”而不是“一次做得完美”。5.2 从笔试题到面试的衔接把卷子变成谈资笔试结束不等于任务完成。我有个习惯走出考场后立刻把自己没答上来的题抄到手机备忘录里回去逐个查资料搞懂。这不仅是补知识更是为后面的面试做准备。因为面试官往往会在面试里顺着笔试题问尤其是那些大家都容易错的点。比如笔试考了HashMap为什么线程不安全面试可能就会继续问“那用ConcurrentHashMap就绝对安全吗它和Hashtable的区别是什么”如果笔试后你没有把源码读一遍这些问题很容易答得空泛。反过来如果你把笔试错题整理成了一份“知识树”从每个考点往外延伸一层面试就会变成你展示深度的舞台。我当时给自己定了一个规矩一份笔试题做完不管过没过至少要总结出三个“之前不会、现在会了”的知识点。这个习惯让我在连续几场笔试之后基本功肉眼可见地扎实了很多。现在带新人我也让他们这么做效果很好。5.3 2015年的题放到现在还有效吗我的判断是绝大多数考点依然有效。Java语言的基础、并发、JVM、集合框架这些内容虽然版本在更新但核心原理没有变。HashMap在JDK8里引入了红黑树但“为什么线程不安全”这个问题的答案依然成立JVM堆内存模型在G1、ZGC时代有了演进但“哪些区域会OOM”的底层逻辑还是那一套。如果说有什么变化那就是现在面试更注重源码阅读和实际调优经验。2015年你只要能说清楚“HashMap是数组加链表”就能过现在面试官很可能追问“链表转红黑树的阈值为什么是8为什么是64”。所以这份老卷子对现在的价值是它帮你划定了知识地图但地图上的每个地标你都得挖得比当年更深。我个人这些年带过不少新人也出过笔试题有一个很深的体会基础题考的不是记忆而是理解开放题考的不是套路而是思考习惯。如果你能把这份2015年搜狗卷子里暴露出来的每个问题都彻底吃透那么不管哪家公司、哪个年份的JAVA笔试你都有一战之力。这大概就是“古早题复盘”的真正意义。最后再分享一个小技巧每道错题都当成一个知识树节点往深挖三层而不是简单背答案你会发现所谓八股文其实都是实战经验的浓缩。
返回列表