ARTICLE DETAIL

资讯详情

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

奇安信2019春招笔试题(二)复盘:Java基础与C语言指针详解

奇安信2019春招笔试题(二)复盘:Java基础与C语言指针详解 奇安信2019春招笔试题二的完整复盘。这批题目我当年是踩着截止时间交的卷后来在牛客上跟人对答案才发现错了好几道尤其是指针那块几乎是全军覆没。这篇博文会把整套题按题型拆开讲清楚每道题考察的知识点、解题思路以及我当时踩的坑和后来复盘总结的经验适合正在准备网络安全方向校招、或者想系统补一下Java和C语言基础的同学参考。1. 项目背景奇安信2019春招笔试到底考什么1.1 从“二”说起这套题在整个招聘流程中的位置先说个背景。奇安信2019年春季校招的笔试是分批次、分岗位进行的我当时投的是安全研发方向收到的笔试邀请里明确写着“春招笔试题二”。这意味着同一个招聘周期里至少有两套不同的试卷大概率是题库抽题再组合也可能是按投递时间先后分场次出题。所以网上流传的“奇安信2019春招笔试题”其实是很多套题的合集你看到的“二”只是其中之一。这套题的特点是基础题占比高算法题中等偏上安全专业知识直接考察的不多更多是藏在编程题和数据库题里间接考。这个出题思路在当时还挺让很多人意外的——毕竟奇安信是安全公司大家都以为会考一堆渗透测试、加密算法之类的结果大部分时间花在了Java基础、数据结构和Linux命令上。后来我入职后跟当时出题的前辈聊过他的说法是校招笔试的核心目标是筛选“基础扎实、能干活”的人安全知识可以进来再培养但编程底子差的人带起来太费劲。所以这套题本质上是“通用研发岗基础能力测试”安全属性反而不明显。1.2 这套题适合谁来刷、能检验什么能力如果你是正在准备网络安全或后端研发方向校招的同学这套题非常适合拿来当自测卷。它覆盖了四个核心模块Java基础含集合、异常、线程、C语言指针与内存、Linux常用命令与Shell、数据库SQL与范式。这四块恰恰是所有研发岗面试笔试的“基本盘”不管你是投安全岗、后端岗还是测试开发岗都绕不开。我当时刷完这套题最大的感受是它不像LeetCode那样只考算法而是把基础知识点揉进选择题和简答题里考察的是“你有没有真正理解”而不是“你会不会背”。举个例子Java集合那题如果只看过八股文而没自己动过手很容易在两个相似选项之间犹豫。这点后面我会细讲。另外这套题里还有一道关于网络协议的选择题考察TCP三次握手和四次挥手的状态变化这种题在安全岗笔试题里几乎必出因为不管是做渗透还是做流量分析TCP状态机都是底层基本功。2. 核心题型拆解从Java基础到数据结构2.1 选择题部分Java集合与并发的高频考点选择题大概是整套卷子里最“杀人”的部分一共20道覆盖Java、C语言、网络、数据库、Linux五个方向。先说Java相关的几道。第一道是关于HashMap的在JDK 1.8中HashMap在什么条件下会将链表转换为红黑树A. 链表长度超过8且数组长度超过64B. 链表长度超过8C. 链表长度超过7且数组长度超过32D. 链表长度超过6。正确答案是A。很多同学只记得“链表长度超过8转红黑树”忽略了数组长度这个前置条件。JDK 1.8的源码里写得很清楚treeifyBin方法中第一步就是判断tab.length MIN_TREEIFY_CAPACITY (64)如果小于64只会做resize扩容不会转红黑树。这个设计意图是为了避免在数组长度还很小的时候因为哈希碰撞集中就盲目转树反而增加了树化的开销。第二道是ConcurrentHashMap的在JDK 1.8中ConcurrentHashMap抛弃了Segment分段锁改为使用什么机制保证线程安全A. synchronized加CASB. ReentrantLockC. volatile加ReentrantLockD. 无锁。正确答案是A。这里考察的是对JDK版本演进的理解。1.8的ConcurrentHashMap在put操作时如果对应的桶位为空就直接用CAS插入如果桶位不为空则对头节点加synchronized锁。这个改动让锁粒度更细并发度更高同时代码也简化了很多。如果你在简历里写了“熟悉Java并发编程”这道题答错基本就凉了。第三道是线程池的ThreadPoolExecutor执行任务时当核心线程数已满且任务队列已满此时提交新任务会触发什么行为A. 直接丢弃任务B. 创建非核心线程执行C. 抛出RejectedExecutionExceptionD. 阻塞等待。正确答案是C。这里容易混淆的是B和C的顺序。正确流程是先判断核心线程是否已满满了就丢进任务队列队列满了再判断线程池是否达到最大线程数没达到就创建非核心线程如果已达到最大线程数才会执行拒绝策略。默认的AbortPolicy就是抛出RejectedExecutionException。我当年选的是B就是没搞清楚“队列先满”还是“线程先满”的顺序。2.2 C语言指针与数组最容易翻车的部分C语言部分的选择题里有一道特别经典int a[5] {1, 2, 3, 4, 5}; int *p (int *)(a 1); printf(%d, *(p - 1));问输出是什么。答案是5。这道题考察的核心是“数组指针”和“指针加减”的关系。a拿到的是整个数组的地址类型是int(*)[5]对它加1相当于跨越了整个数组的长度也就是5个int的距离。所以p实际上指向的是a[5]这个越界位置。p - 1则往后退一个int指向a[4]输出就是5。这道题我当年写的答案是1因为我把a 1理解成了a 1。这两个的区别必须刻在脑子里a作为表达式是数组首元素的地址类型是int*加1只跳过一个元素a是整个数组的地址类型是int(*)[5]加1跳过整个数组。还有一道关于指针运算的char *s hello; printf(%c, *(s 2));这个相对简单输出是l但我觉得它考察的是“字符串常量存储在只读区不能通过指针修改内容”这个点。如果题目换成char *s hello; s[0] H;这段代码在运行时大概率会崩溃因为字符串常量在Linux下是写入.rodata段的只读不能改。如果你写的是char s[] hello;那就可以改因为它是数组存储在栈上。2.3 网络与数据库基础中的基础网络部分有一道TCP的题TCP连接建立过程中第二次握手时服务器端发送的报文段中SYN和ACK标志位的状态是什么答案是SYN1, ACK1。这道题本身不难但它引出的问题是为什么第二次握手需要同时置位SYN和ACK因为服务器既要回应客户端发来的SYN用ACK确认又要向客户端发起自己的SYN表示自己也想建立连接。理解了这个“捎带确认”的机制整个三次握手的过程就顺了。数据库部分有一道关于SQL查询的有两个表学生表学号姓名班级和成绩表学号课程号成绩查询“所有课程成绩都大于80分的学生姓名”用SQL怎么写当时的选项里有一个用NOT EXISTS的写法这个才是对的先找出“存在成绩小于等于80分的学生”然后用NOT EXISTS排除掉这些学生。如果直接写WHERE 成绩 80再用GROUP BY 学号会把那些“有一门成绩大于80但其他科目不及格”的学生也算进去属于典型的逻辑错误。还有一道关于数据库范式的选择题某个关系模式中所有属性都是主属性那么这个关系模式最高一定满足什么范式答案是第三范式3NF。这里有个定理如果关系模式的所有属性都是主属性那么它一定不存在非主属性对码的部分函数依赖和传递函数依赖所以至少是3NF。但能不能达到BCNFBoyce-Codd范式就不一定了因为可能存在主属性之间的传递依赖。这道题考得比较深我当时是完全不会后来复习数据库时才弄明白。3. 精选真题实战手写代码与原理解析3.1 编程题一数组去重与排序的多种实现编程题第一道是给定一个整数数组去除重复元素并按照从大到小的顺序输出。这道题看起来很基础但可以考察出很多层面的能力。最直接的思路是用Java的TreeSet它天然支持排序和去重但默认是升序需要倒序的话可以传入Collections.reverseOrder()。代码大概是public static void main(String[] args) { int[] nums {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}; SetInteger set new TreeSet(Collections.reverseOrder()); for (int num : nums) { set.add(num); } System.out.println(set); }如果面试官要求不能用现成的集合类那就得手写先手写一个快速排序再去重输出。这里有个小技巧排完序后重复元素必然相邻所以输出时只需要比较当前元素和前一个元素是否相同就能自然去重。时间复杂度是O(n log n) O(n)空间复杂度是O(1)如果不算排序的递归栈空间。我当时提交的版本是用HashMap统计频次再把key取出来排序。这个思路也能过但明显绕了弯路因为频次在这里根本不需要只要判断是否出现过就够了。用HashSet标记比用HashMap计数更简洁也更能体现你对集合类的理解。这种“用对工具”的判断力恰恰是笔试想考察的。3.2 编程题二单链表反转的迭代与递归实现第二道是链表反转。这道题在2019年前后几乎成了校招笔试的标配因为它既能考链表基础操作又能量出迭代和递归两种思维。这个题我只能用C语言写然后手机拍照上传过程相当折磨。题目是给定一个单链表返回反转后的链表。迭代写法核心是三指针pre、cur、next。每次循环先把cur.next存到next再把cur.next指向pre然后pre和cur整体往后移动。注意循环结束后pre指向的是新链表的头节点不是cur。我当时就是没处理好这个边界写了一堆越界。struct ListNode* reverseList(struct ListNode* head) { struct ListNode* pre NULL; struct ListNode* cur head; while (cur ! NULL) { struct ListNode* next cur-next; cur-next pre; pre cur; cur next; } return pre; }递归写法更短但理解难度高一些。核心思路是先反转head后面的所有节点然后让head.next.next指向head再让head.next指向NULL。递归的终止条件是head NULL || head-next NULL。我当年面试时被要求用递归写一遍直接卡壳了。后来自己画了无数次链表图才真正理解“递归是从后往前翻转”这件事。3.3 编程题三括号匹配与栈的应用第三道编程题考的是括号匹配给定一个只包含(,),{,},[,]的字符串判断括号是否有效。有效条件是左括号必须用相同类型的右括号闭合并且左括号必须以正确的顺序闭合。这题的标准解法是栈。遇到左括号就入栈遇到右括号就弹出栈顶元素检查是否匹配。有两个细节很容易翻车第一栈空了还有右括号说明不匹配第二字符串遍历完但栈不为空说明还有左括号没闭合。public boolean isValid(String s) { DequeCharacter stack new ArrayDeque(); for (char c : s.toCharArray()) { if (c ( || c { || c [) { stack.push(c); } else { if (stack.isEmpty()) return false; char top stack.pop(); if (c ) top ! () return false; if (c } top ! {) return false; if (c ] top ! [) return false; } } return stack.isEmpty(); }这道题之所以值得写进博客是因为它背后是“栈”这种数据结构在编译器语法分析、表达式求值里的实际应用场景。理解了括号匹配就理解了栈“最近匹配”的核心特性后面再做逆波兰表达式求值、简化路径这类题思路会顺畅很多。4. 易错点与高频失分场景实录4.1 数组与指针的“地址”陷阱这套笔试题里C语言部分的失分重灾区就是对“数组名”和“取地址”的误解。这里我整理一个对比表格把最容易混淆的几种情况列在一起表达式类型含义加减1的效果aint*首元素地址跳过一个inta[0]int*首元素地址跳过一个intaint(*)[5]整个数组的地址跳过整个数组*aint首元素的值不需要加减理解这张表的核心是a和a虽然打印出来的地址数值一样但是它们的“步长”完全不同。a的步长是sizeof(int)a的步长是sizeof(int) * 数组长度。指针运算的本质是“地址 步长 × 数量”而不是简单的“地址 字节数”。这个理解不到位*(p - 1)这类题基本只能靠猜。4.2 Java集合类选择什么时候用ArrayList、LinkedList、HashMapJava选择题里有一道关于集合的频繁在列表中间插入和删除元素应该优先选择哪个实现类答案是LinkedList。这个结论本身不难但我想借这道题多说一点。LinkedList是基于双向链表实现的插入和删除只需要修改指针引用时间复杂度O(1)前提是已经定位到插入位置而ArrayList是基于动态数组实现的中间插入需要把后续所有元素往后移动最坏情况O(n)。所以“随机访问多选ArrayList中间增删多选LinkedList”这条经验法则在绝大多数场景下是成立的。但这道题的深水区在于JDK 1.8之后LinkedList的插入性能并没有想象中那么好因为每次插入都要new一个Node对象如果数据量大会引发频繁的GC。现代开发里如果数据量在几千级别的列表LinkedList和ArrayList的差距几乎感觉不到而ArrayList因为内存连续、CPU缓存友好很多时候反而更快。所以面试官如果追问“你确定吗”你需要能说出这个层次的分析而不是只会背结论。4.3 Linux命令与Shell易混淆命令速查Linux部分的选择题考了grep、awk、sed、find这几个命令的区分以及chmod权限数字的换算。find和grep是很多人最先混淆的一对。find是“找文件”的根据文件名、时间、大小等元数据搜索grep是“找内容”的在文件内部搜索匹配的文本行。如果题目说“查找当前目录下所有包含‘error’字样的日志文件”正确思路是先用find定位文件再用grep匹配内容或者直接用grep -r error ./。chmod的权限数字rwx分别对应4、2、1。所以chmod 755就是rwxr-xr-x文件所有者可读写执行组用户和其他用户只能读和执行。这里有个细节数字权限只影响文件或目录的“基本权限”不涉及ACL、s权限位等扩展属性而这些在安全相关题目里也偶尔会出现。比如chmod us file是给文件添加SetUID权限执行该文件时会以文件所有者的身份运行这在系统安全考题里属于风险点。4.4 数据库索引失效与SQL书写规范数据库部分有一道简答题在什么情况下即使查询条件中使用了索引列索引也可能失效我当时的回答只写了“对索引列使用函数”其实这只是冰山一角。完整答案至少包括以下几种场景对索引列使用函数或表达式计算比如WHERE YEAR(create_time) 2020会让索引失效应该改成WHERE create_time 2020-01-01 AND create_time 2021-01-01。使用LIKE以通配符开头比如LIKE %abc索引失效。隐式类型转换比如索引列是字符串类型但查询条件用的是数字数据库会做类型转换导致索引失效。OR连接的条件中如果其中一个条件没有索引整个查询可能不走索引。笔试里能答出前两三条就能拿到大部分分数但如果想拿满分最好把“隐式类型转换”和“OR条件”也写全。我当时就漏了这两个后来复盘时才发现其实这些在日常SQL优化里非常常见尤其是隐式类型转换是个极其隐蔽的坑。5. 备考策略与个人经验从这套题看校招笔试的底层逻辑5.1 时间分配选择题控制在30分钟内编程题留足时间这套题我印象中总时长是90分钟题型分布是20道选择题、3道编程题、2道简答题。我犯的第二个错误就是在选择题上消耗了太多时间导致最后一道编程题只有15分钟匆匆忙忙提交了一个有bug的版本。如果现在让我重新分配时间我会这样做选择题全程控制在30分钟以内遇到卡壳的题先标记不纠结编程题每道预留15到20分钟先写核心逻辑、再补边界条件简答题放在最后10分钟用关键词短句答题不需要长篇大论。这里要特别提醒笔试系统大多是“每道题单独提交”的有些平台甚至不允许往回翻。所以“标记跳题”这个策略不一定适用于所有在线笔试系统。开考前一定要先花一分钟看清楚平台的答题规则。5.2 复习优先级先补Java和C语言再刷算法题从我后续参加的多场笔试经验来看网络安全方向的校招笔试题基础知识的覆盖面非常广但深度通常不会超过“本科课程设计”的难度。所以复习顺序建议是Java集合与并发、C语言指针与内存管理、数据库SQL与范式、Linux常用命令最后才是LeetCode刷题。“基础四件套”刷两遍比“算法题刷200道”管用得多。原因很简单选择题占的分值比例往往更高而选择题考的就是基础概念的准确理解算法题虽然分值高但难度大、区分度低大部分人的得分都集中在通过率在50%以上的简单题和中档题你能保证简单题全对、中档题做出一大半总分就已经很能打了。我有个学弟后来跟我分享他刷了300道LeetCode结果笔试时选择题错了8道直接挂掉。后来他二战同一家公司时花了三周时间专门夯实基础笔试一次通过。这不是个例而是校招笔试的底层逻辑选择题筛掉的是基础不牢的人编程题筛掉的是动手能力差的人两者缺一不可。5.3 关于“背题”的迷思参考答案存在的意义写这篇博客的时候我在网上搜索了“奇安信2019春招笔试题”的相关讨论发现很多人都在求答案。我能理解这种心态但我必须泼一盆冷水笔试题的答案靠背是背不完的。因为同一家公司在同一招聘季可能会放出三四套题就算你背熟了“二”的答案遇到“三”“四”照样抓瞎。正确的方式是把每道题当作一个“知识点索引”先看这题考的是什么再回到教科书或网上教程里把这个知识点吃透。比如你看到“HashMap链表转红黑树”这道题不应该只记“长度超过8、数组长度超过64”而是要把HashMap的put流程完整看一遍理解为什么需要这个阈值、为什么数组长度优先扩容。等你真正理解了设计者的意图题目怎么变都难不住你。5.4 写在最后一道题答错不代表整个人不行我在准备这篇博客时回头看当年的答卷发现错了差不多一半的选择题。如果以现在的标准衡量这套卷子我大概是过不了线的。但后来的事情大家也知道了我依然进入了这一行并且在这些踩过的坑上积累了比“全对”更有价值的东西。所以我想对正在准备校招的同学说笔试题的目的是“筛人”但不是为了证明你“不行”。一套卷子没考好只能说明你在这几个知识点上还有盲区补上就好。真正重要的不是把每一道题都做对而是通过这套题搞清楚自己离“能干活”还差多远。我个人的体会是把一套真题研究透比“浏览”五套真题的效果好得多。研究透的标准是看到一道题你能说出它考察的知识点是什么、常见的变形有哪几种、易错点在哪个环节。达到这个状态你就不怕这套题本身了怕的是你根本没到“研究透”的那一步。希望这篇复盘能帮你少走一些弯路。
返回列表