行业资讯
📅 2026/9/1 22:04:24
帆软软件研发岗笔试题复盘:从Java基础到Linux命令考点全解析
2018年秋天我是在南京参加的帆软软件秋招研发岗笔试。那时候帆软在圈子里已经很有名了做FineReport和FineBI属于国产报表和商业智能工具里绕不开的一家。拿到试卷的第一反应是题目不偏、不怪但覆盖面非常广从Java、C/C基础到数据结构、操作系统、数据库、Linux几乎把研发岗该有的基本功都点了一遍。后来和几个一起笔试的同学对完答案大家的共识是这套题考的不是你背了多少八股而是你平时写代码的时候有没有真正想过底层原理。这篇文章就围绕帆软软件2018届秋招研发岗笔试题把这个类型笔试题的出题逻辑、典型考点、答题策略完整复盘一遍。无论你正在准备帆软还是打算投国产软件厂商的研发岗这篇内容应该都能帮你少走点弯路。尤其是“数组和指针”“Java集合底层”“Linux常用命令”这类热搜词背后的考点我会结合具体的题目形态来讲尽量还原笔试现场的感觉。1. 这套卷子出题的核心逻辑它到底想筛掉哪种人1.1 帆软研发岗在做什么决定了它考什么在聊题目之前得先搞清楚一个前提帆软这种公司研发岗位到底是干嘛的。帆软的核心产品是FineReport和FineBI一个是报表工具一个是商业智能分析平台。这类产品有很典型的特点后端要处理大量数据运算前端要画各种复杂的图表和报表中间还有报表引擎、数据缓存、权限控制一堆模块。所以它对研发的要求不是“会用框架写接口”而是“对语言底层、数据结构、操作系统原理有扎实理解因为报表引擎的性能优化全靠这些基础”。这也就解释了为什么笔试里面Java、C/C、数据结构、操作系统占比那么高。它不是在考你会不会某个框架而是考你有没有能力在复杂的性能场景下把问题拆清楚、把代码写对。1.2 和互联网大厂笔试题的差异偏基础、重广度拿我当时对比过的几家互联网公司笔试题来说大厂特别喜欢考“脑筋急转弯”型的算法题比如动态规划、贪心、复杂贪心场景题目一看就要想很久。帆软的笔试则不太一样它的算法题更偏向“基础扎实就能解出来”的类型但是题量很大广度很宽。我印象里整张卷子大概分了几个模块语言基础题、数据结构与算法题、操作系统题、数据库和SQL题、Linux基础题还有一两道开放性的简答题。每个模块都有很多道小题不是一个模块一两道而是十几道甚至几十道地铺开。这就要求你在考试的时候不能在某一道题上死磕不然后面的题根本来不及写。1.3 从一个参加者的视角看整场体验笔试是线下的纸质试卷大概两个小时到三个小时。拿到卷子第一感觉是“怎么这么多题”甚至有一点像学校期末考的综合卷而不像网上很多人描述的互联网公司笔试那个味道。但这也正是它的特点覆盖面广、题量足、注重基础。所以如果你想准备这类笔试最忌讳的就是只刷算法题。你得把Java基础、C语言基础、数据库、Linux都过一遍缺哪块都有可能丢分。2. 语言基础题Java和C/C的考点拆解2.1 Java考点集中在集合、String和JVM作为一家以Java为主要开发语言的公司Java相关题目在卷子里的分量很重。常见的考点就那几个方向集合类源码、String类特性、JVM内存结构、并发基础。集合类里HashMap是绝对的重点。我印象里类似“HashMap的底层数据结构是什么”“JDK1.7和JDK1.8的HashMap有什么区别”“HashMap为什么是线程不安全的”这种题目反复出现。连环炮式地问其实就是看你对源码有没有真正读过。以JDK1.8的HashMap为例底层是数组加链表加红黑树。当链表长度超过8且数组长度大于等于64时链表会转成红黑树目的是把查询复杂度从O(n)降到O(log n)。但很多人忽略了一个点为什么是8这是根据泊松分布算出来的一个阈值在负载因子0.75的情况下链表长度达到8的概率已经非常低。所以“为什么是8”这种追问答得出来的人是真读过源码的。String类也是高频考点。核心就是“String是不可变的”这个不可变性体现在String类被final修饰、内部char数组被final修饰且没有对外暴露修改方法。然后面试官喜欢接着问“StringBuilder和StringBuffer的区别”说白了就是一个线程不安全但效率高一个加了synchronized所以线程安全。我当时答题的时候还特别注意了一个细节字符串常量池。比如String s1 abc; String s2 new String(abc);这种题目考的是s1和s2是否相同、s1和s2的intern()之后是否相同。这道题看着简单但如果没搞清楚常量池和堆的区别很容易答错。2.2 C/C重点数组和指针的相爱相杀热搜词里有“数组和指针笔试题”这确实也是研发岗笔试里的常客。我印象比较深的一道题目是问“数组名和指针的区别”。很多人觉得数组名就是指针这在大多数场景下用起来没问题但考试就是考细节。数组名是一个地址常量不能被赋值也不支持自增自减指针是一个变量指向某个内存地址可以被重新赋值。sizeof(arr)对一个数组返回的是整个数组占用的字节数而sizeof(ptr)返回的是指针本身占用的字节数在32位系统上是464位系统上是8。典型的坑题是这样的char str[] hello; char *p str; printf(%lu\n, sizeof(str)); // 6包含末尾的 \0 printf(%lu\n, sizeof(p)); // 864位系统上指针大小 printf(%lu\n, strlen(str)); // 5不包括 \0这种题目我刷过好几次每次都有同学栽在sizeof和strlen的区别上。简单总结就是sizeof是编译时运算符关心的是类型占多少内存strlen是函数运行时统计字符串长度遇到\0才停下来。数组退化指针后sizeof的结果就变了这是做C语言题目最常见的陷阱。2.3 复杂度和边界条件语言题里暗藏的基础功有些题目表面上是语言基础实际考的是算法思维。比如“给定一个字符串怎么判断是不是回文”“怎么去除数组里的重复元素”。这类题如果只答出最普通的解法得分一般如果能主动说出时间复杂度、空间复杂度以及边界情况怎么处理就能拉开差距。举个例子判断回文字符串最直接的做法是双指针从两端往中间走。但如果题目变成“字符串里带空格和标点只判断字母数字”或者“不区分大小写”很多人就忘了处理。写代码之前先把边界条件列出来这种习惯在笔试里特别加分。3. 数据结构与算法手写题拉开差距的核心战场3.1 链表类题目反转链表是“必考题”级别的存在数据结构部分链表题是出现频率最高的。其中“反转链表”几乎是标准配置。当时试卷上就有一道反转单链表的题要求手写代码。反转链表的迭代写法核心是三个指针前驱节点pre、当前节点cur、后继节点nextTemp。每轮循环里先把cur.next存下来再把cur.next指向前驱最后三个指针整体后移。这种题看着简单但手写的时候很多人会忘记“先把下一个节点存下来”这个步骤导致链表断掉。public ListNode reverseList(ListNode head) { ListNode prev null; ListNode cur head; while (cur ! null) { ListNode nextTemp cur.next; cur.next prev; prev cur; cur nextTemp; } return prev; }除了反转“判断链表是否有环”也经常考。常规思路是快慢指针快指针每次走两步慢指针每次走一步如果有环两者必然相遇。注意边界条件链表为空或者只有一个节点时返回false。这类题考的不是智商是熟练度。建议面试前把这几种链表题反复写几遍写到不用思考就能敲出来为止。3.2 栈和队列两个栈实现队列的经典思路栈和队列的题目最经典的莫过于“用两个栈实现一个队列”。这个题我当时也碰到了虽然不要求写完整代码但要画出思路图并解释。核心思路是一个栈负责入队操作stackIn一个栈负责出队操作stackOut。入队时直接压入stackIn出队时如果stackOut为空就把stackIn里的所有元素逐个弹出并压入stackOut再从stackOut弹出栈顶元素。这样做的本质是利用栈的“后进先出”特性通过两次反转实现“先进先出”。这种题目考的是你对数据结构的理解深度而不是单纯背代码。我当时回答的时候还补充了一句如果两次连续出队stackOut不为空的时候不需要搬移数据这样均摊时间复杂度是O(1)说完明显感觉阅卷的考察点是踩到了。3.3 排序算法快排必须能手写排序算法在研发岗笔试里是必选项。常见考法有两种一种是直接要求手写快排或堆排另一种是给一段排序代码问时间复杂度、稳定性、适用场景。快排手写的关键点是partition函数。我总结过一套稳定的写法取数组最右边的元素作为基准值用两个指针i和jj从左往右扫遇到小于等于基准值的就交换到i位置i右移一位。最后把基准值换到i位置返回i作为分割点。public void quickSort(int[] arr, int left, int right) { if (left right) return; int pivotIndex partition(arr, left, right); quickSort(arr, left, pivotIndex - 1); quickSort(arr, pivotIndex 1, right); } private int partition(int[] arr, int left, int right) { int pivot arr[right]; int i left; for (int j left; j right; j) { if (arr[j] pivot) { swap(arr, i, j); i; } } swap(arr, i, right); return i; }快排是不稳定排序平均时间复杂度O(n log n)最坏情况O(n²)。最坏情况发生在数组已经有序且每次基准值都选到最大或最小元素时。我当时还写了一句优化思路可以采取“三数取中”选基准值避免最坏情况。这种细节能体现你确实理解排序算法而不是背模板。堆排也经常考重点在于“建堆”和“堆调整”两个步骤如果考到优先队列的底层实现其实就是堆。我当时复习的时候把堆排和Top K问题放在一起看的因为“找出数组里最大的K个数”这类题用堆排的思路做是最高效的方案。3.4 算法题之外的思考时间复杂度和空间复杂度都要写手写算法题的时候建议主动把时间复杂度和空间复杂度写出来。阅卷人扫一眼就能判断你对这道题有没有完整思考。比如快排是O(n log n)平均归并是稳定排序但空间复杂O(n)堆排是原地但稳定性和常数性能都不如快排。这些在面谈的时候也会被追问笔试的时候写清楚能省很多后续解释的麻烦。另外边界条件真的是丢分重灾区。我当时写“二分查找”的时候就特别检查了mid的计算mid left (right - left) / 2而不是(left right) / 2因为后者在left和right都很大的时候可能溢出。这种细节平时不注意考场上根本反应不过来。4. 操作系统、数据库和Linux容易被忽略的得分区4.1 进程与线程从基础概念到场景分析操作系统题目在帆软笔试里占的比例不低尤其是进程和线程相关内容。它不像算法题那样需要大段coding但考察点特别细。进程和线程的区别是基础中的基础。进程是资源分配的基本单位线程是CPU调度的基本单位。同一个进程内的多个线程共享堆内存和方法区但每个线程有自己的虚拟机栈和程序计数器。这个“哪些共享、哪些独立”是特别容易出选择题或判断题的点。然后经常顺带问“进程间通信有哪些方式”常见答案包括管道、消息队列、共享内存、信号量、Socket等。其中共享内存是效率最高的进程间通信方式因为它不需要在内核态和用户态之间反复拷贝数据但需要注意同步互斥问题。我还遇到一种题是给一个场景问用多进程还是多线程。比如“一个报表系统需要并发处理多个用户的导出请求”这种场景用线程池更合适因为任务是CPU密集和IO密集混合的且线程创建销毁比进程轻量得多。这种题目没有标准答案关键是说清楚理由。4.2 死锁四个条件必须背熟死锁也是操作系统模块的高频考点。核心就是四个必要条件互斥、持有并等待、不可剥夺、循环等待。题目通常有两种问法一是直接列出这四个条件二是给一个具体场景问你“是否可能死锁为什么”。我印象比较深的一道判断题是两个线程分别持有锁A和锁B线程1先拿A再拿B线程2先拿B再拿A问是否可能死锁。答案是可能因为线程1拿到A后等待B线程2拿到B后等待A形成了循环等待。避免死锁的方法也可以从四个条件反推破坏互斥比较难因为锁本身就是要互斥的破坏持有并等待就是一次性申请所有资源破坏不可剥夺就是强占破坏循环等待就是对资源编号按顺序申请。这类题目背熟四个条件之后逻辑推导就能通。4.3 数据库SQL多表查询是送分题也是送命题数据库模块主要是SQL题常见的是多表查询。帆软做报表和BI对SQL能力的要求自然不低所以这块考得不难但很实际。有一道典型的题目是“查询每个部门中工资最高的员工”。这个你必须用到子查询或者窗口函数。如果数据库是MySQL 8.0可以直接用ROW_NUMBER窗口函数SELECT department_id, employee_name, salary FROM ( SELECT department_id, employee_name, salary, ROW_NUMBER() OVER(PARTITION BY department_id ORDER BY salary DESC) AS rn FROM employee ) t WHERE t.rn 1;但如果是MySQL 5.7及以下没有窗口函数就需要用关联子查询。我当时在卷子上写的是先建临时表查每个部门的最高工资再join原表取员工信息。这种写法在面试里也很常见关键在于写对了没有。还考过一张典型的“学生-课程-成绩”三表查询比如“查询选了所有课程的学生姓名”。这类题的思路是先找出课程总数再按学生分组统计选课数量最后比较数量是否等于课程总数。用HAVING COUNT(DISTINCT course_id) (SELECT COUNT(*) FROM course)这种套路可以解决。SQL题其实是整张卷子里最容易突击涨分的部分把所有多表查询类型练一遍基本就能应付。4.4 Linux命令研发岗必须掌握的基本功Linux相关题目在热搜词里也出现了所以我把这一块重点说一下。备考的时候可能觉得Linux只是辅助技能但如果考到通常都是“给场景写命令”这种很直接的题。比如“查看端口8080被哪个进程占用”答案就是lsof -i:8080 # 或 netstat -tlnp | grep 8080再比如“查看系统内存使用情况”可以用free -h“查看磁盘剩余空间”用df -h“查找当前目录下所有包含‘error’的日志文件”用grep -r error *.log。Linux题还有一个常考的点是文件权限。chmod 755、chmod 644这类数字含义必须一秒钟就能反应过来。7是rwx5是rx6是rw。有时候还会考chmod ux这种符号模式的意思本质都是权限位管理。我当时复习Linux的方式是把常考命令按功能分类整理成一张表比如进程管理、文件操作、网络命令、磁盘管理、权限管理考试前快速过一遍。不用每个命令的每个参数都背但高频命令和参数要熟练。场景常用命令查看端口占用lsof -i:8080 或 netstat -tlnp查看进程ps -ef 或 top查看内存free -h查看磁盘df -h查找文件find . -name *.log搜索内容grep -r 关键字 .权限修改chmod、chown5. 笔试现场的三小时时间分配和答题策略5.1 拿到卷子先花五分钟通读笔试一开始不要急着做题。我那次笔试拿到卷子之后先花几分钟把整张卷子翻了一遍看看哪个模块题量大、哪个模块分值高、有没有一眼看过去就能答的题。这样做的原因是不同模块的题量分布可能跟你预期不一样。比如有一年Java题特别多Linux只考了两三道再比如某年数据库SQL出了大题你没提前看到就容易在前面浪费太多时间。通读卷子可以帮你快速建立全局策略。关于选择题和填空题建议是快速过会的立刻选不会的先圈起来跳过不要在一道选择题上纠结超过一分钟。选择题答错通常不扣分所以实在不会的题可以凭感觉选一个不要留空。5.2 答题顺序先拿基础分再啃硬骨头我的策略是“先做语言基础和数据库题再做操作系统和Linux最后做算法手写题”。为什么这样排序因为语言基础是纯粹的知识点记忆型题目状态最好的时候准备充足先做基本就是送分。数据库和Linux也是类似的会就是会不会磨也没用。而算法题需要比较长的时间思考放在最后不会影响前面拿分而且有时候前面的题目会给你一些灵感。有一种情况要特别注意如果算法题分值很高不能完全留到最后。我一般先看一遍算法题如果需要5分钟内就有思路就立刻做如果看着没有思路就先去做基础题最后再回来慢慢磨。5.3 编程题的时间控制与“部分得分”策略手写代码题最容易犯的错就是追求完美。我第一年参加笔试的时候就吃过亏一道反转链表题非要写递归写法结果卡在递归的返回条件上浪费了十几分钟后面数据库大题都没写完整。后来我总结了一个经验手写代码首先保证“能跑通”的解法不要一开始就追求最优解。具体操作就是先用最朴素的思路把代码写出来保证逻辑正确然后再去考虑优化。比如题目要求数组去重最直接的办法是用Set虽然空间复杂度是O(n)但代码短、不容易出错。如果要求原地去重且数组已排序再用双指针。先把分拿到手再考虑加分项。另外如果代码写不完也要把思路用注释写清楚。比如“这里应该递归反转右子树”“这里用二分查找找到第一个大于target的位置”把思路写出来让阅卷人看到你是有方向感的至少能拿一部分分。5.4 开放性问题别只给结论要展示思考过程帆软的笔试有时候会在最后放一两道开放性问题。我遇到过的类似题有“如果报表导出非常慢你会怎么排查”“如何设计一张大宽表来支持亿级数据的查询”。这类题没有标准答案但最忌讳的是只写一两句话。阅卷人想看的是你的分析框架先定位问题类型再列出可能的原因再给出对应的解决路径。以“报表导出慢”为例我会分四步来分析第一步看是单条SQL慢还是整体流程慢第二步看数据量级和索引使用情况第三步看是否可以做分页、异步或缓存第四步看是否有资源瓶颈CPU、数据库连接数、网络带宽。即使方案不一定最优但只要体现你是按“发现问题→定位原因→设计方案→验证效果”的思路来思考的就会比那种写“换更好的服务器”的答案强很多。开放题放在最后还有一个作用它其实是压舱石。前面基础题如果丢了一些分开放题答得有条理能拉回不少印象分。毕竟这类题考察的是工程思维而工程思维说到底就是一套处理问题的套路。写在最后的一点个人体会把笔试复盘了一遍想再说说我的真实感受。帆软这套笔试题的难度不在单题而在广度。它不会用一道特别变态的算法题来卡你但会通过覆盖各个维度的基础题把知识体系不完整的人筛出来。所以准备这类笔试最有效的策略不是刷一堆难题而是把Java、C基础、数据结构、操作系统、数据库、Linux这几块的基本功打扎实。我当时用的是“每周末把常考题型过一遍”的方式一个模块一个模块地扫最后的结果是笔试顺利通过后面面试的时候这些基础也帮我撑住了很多追问。希望这篇复盘能给你一个清晰的备考地图剩下的就是静下心把每一个基础考点真正吃透。