白犀牛前端实习一二面:日志队列组件与二分查找
- 轮次
- 一面+二面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 简短自我介绍
- 上一份实习是九月份结束的吗?怎么刚结束就急着找下一个?上一家为什么不干久一些?
- 挑一个你自己觉得能体现能力的项目介绍
- 队列是怎么做的?(面试官评价:感觉就是一个 switch 语句,也没什么难的)
- 项目 A 有没有参考市场上已有的、开源的方案?你做了哪部分?
- 你刚刚说这个项目主要是设计了一个类,这个类里有什么东西?怎么用的?
- 如果有时候一次性有很多数据怎么办?日志打得特别快,你这个队列有容量限制吗?
- 从组件自身出发,别人来用这个通用组件时,要考虑哪些异常情况或错误的使用场景?如何限制或建议别人怎么用?
- 这个项目做了多久?好像也没做多久吧,你怎么不介绍实习的公司项目?
- 文件上传有什么特殊的技术点需要考量吗?
- 用户看到的上传进度是后端管理的还是前端管理的?
- 实习的项目里你觉得你做得比较好的地方是哪些?
- 参加过 ACM 是吧,来写个算法题吧,力扣 24
- 大四了,没有考研的打算吗?
- ACM 是学校的还是别的?
- 主要开发偏前端是吧,python 这些会吗?后端语言什么的?
- 计算机的专业课都有学对吧,你专业课里面学得比较好的有什么?
- TCP 握手
- 操作系统你学的是什么操作系统?
- 操作系统的临界区是什么?
- 用代码如何实现(临界区)?实现一个锁,怎么操作,写代码
- 算法题:一个数组先递增后递减,查找元素返回下标,要求 O(log n) 时间复杂度
《参考解析》
1. 日志队列组件怎么设计。这道题被追着问了三轮,核心是「队列有没有上限、满了怎么办」。日志上报队列必须有容量上限——无界队列在消费端跟不上时,先爆的是内存;满了以后要有明确策略:丢最旧、丢最新,或者降级为批量落盘,且丢弃必须计数并暴露出来,不能静默丢。生产者入队必须非阻塞,不能因为上报把主流程卡住;消费端做批量聚合(比如 200ms 或攒够 50 条一起发),请求数能降一到两个数量级。作为「别人来用」的通用组件,接口要能表达异常语义:enqueue 返回是否成功、flush 用于页面卸载时兜底、dispose 要保证之后不再有回调触发;重复初始化、并发调用、队列容量为 0、上报接口抛错这些都要有定义并有测试。面试官那句「感觉就是一个 switch 语句」其实是在提示:光有分发逻辑不算组件,边界和错误语义才是。
2. 文件上传的技术点。大文件要分片(常见 15MB 一片):上传前算文件指纹(抽样 hash 或 6,太多会把带宽打满、也会顶到浏览器同域连接数上限;进度按「已完成分片字节数 / 总字节数」算,不要直接透传 crypto.subtle)实现秒传和断点续传,服务端已存在直接返回,续传时只补缺失分片;分片并发数限制在 3xhr.upload.onprogress 的瞬时值,否则并发分片会让进度条回退;失败分片要指数退避重试并给出可手动重试的入口;整个任务要能取消(AbortController);大文件不要一次读进内存,用 File.slice 按片读,读完一片释放一片。
3. 上传进度是前端还是后端管理。要先把「字节进度」和「业务进度」区分开。浏览器能拿到的只有「字节已发送」——xhr.upload.onprogress 或者 fetch + ReadableStream 里的分片计数,这部分属于前端;而业务进度(服务端合并分片、转码、审核)只能由后端管理:前端提交后拿到任务 ID,再轮询或用 SSE/WebSocket 订阅状态。面试里一定要点明为什么不能只靠 onprogress——它到 100% 只代表字节发完了,不代表服务端处理完成,所以大文件上传的进度条通常要做成两段式。
4. 临界区与手写一个锁。临界区是「访问共享资源、必须互斥执行的那一段代码」。裸机上保护临界区是关中断或挂起调度器(taskENTER_CRITICAL),Linux 用户态则用互斥量、自旋锁或无锁原子操作。手写锁的底线是原子指令加内存屏障:用 std::atomic_flag::test_and_set(或 GCC 的 __atomic_test_and_set)抢标志位,抢不到就 pause/yield 忙等,解锁用 release 语义写回;自旋锁只适合极短的临界区,长临界区要用会睡眠的 mutex,否则白烧 CPU。用 Java 写就是 synchronized 或 ReentrantLock,但面试官问「用 Java 写操作系统」想看的是你能不能讲清「原子指令 + 内存序 + 谁在什么时候让出 CPU」这条链,而不是 API 名字。
5. 山脉数组 O(log n) 找目标。对应力扣 1095,两步二分。第一步找峰顶:比较 nums[mid] 与 nums[mid+1],nums[mid] < nums[mid+1] 说明还在上升段,left = mid + 1,否则 right = mid,循环结束时 left 就是峰顶下标。第二步先在前半段升序区间做标准二分;没找到再在后半段降序区间做「方向反过来」的二分(nums[mid] > target 时往右走)。容易写错的三个边界是:数组长度只有 1、目标恰好是峰顶、左右两段的区间开闭重叠。整体 O(log n)、O(1) 空间。
6. 力扣 24 两两交换链表中的节点。迭代法的写法是加哑结点 dummy,每轮记录 prev、first = prev.next、second = first.next,把 prev.next 指向 second、first.next 指向 second.next、second.next 指向 first,然后把 prev 推到 first;终止条件是 prev.next == null 或 prev.next.next == null(奇数个节点时最后一个保持不动)。递归法更短:先存下 second.next,返回 second,并让 second.next = head、head.next = swapPairs(旧 second.next)。面试时主动说清「奇数个节点怎么处理」和「不允许只改值」两个约束,比写完就沉默要加分。