文章目录
- 一、面试问题
-
- [1. 创建索引](#1. 创建索引)
- [2. 拦截器(Interceptor)和过滤器(Filter)的区别](#2. 拦截器(Interceptor)和过滤器(Filter)的区别)
- [3. 为什么jwt令牌代替session?](#3. 为什么jwt令牌代替session?)
- [4. 有一个100行的数据,和一个1万行的数据,写sql 的时候要注意什么?](#4. 有一个100行的数据,和一个1万行的数据,写sql 的时候要注意什么?)
- [5. redis和memcache的区别](#5. redis和memcache的区别)
- [6. linux部分命令](#6. linux部分命令)
- [7. 飞书视频测试功能点](#7. 飞书视频测试功能点)
- [8. 飞书视频性能保障](#8. 飞书视频性能保障)
- [9. 在线协同文档的测试用例](#9. 在线协同文档的测试用例)
- [10. 飞书低代码平台页面的性能保障工作](#10. 飞书低代码平台页面的性能保障工作)
- 二、SQL
-
- [1. 查出表中学生成绩最好的学生信息](#1. 查出表中学生成绩最好的学生信息)
- [2. 2020/12/1 10:28:31这种数据按照小时统计每个不同小时出现数据个数](#2. 2020/12/1 10:28:31这种数据按照小时统计每个不同小时出现数据个数)
- [3. 查询姓王 的同学的个数、年龄第二大的同学](#3. 查询姓王 的同学的个数、年龄第二大的同学)
- 三、手撕算法
-
- [1. 二分搜索二维数组](#1. 二分搜索二维数组)
- [2. K个一组翻转链表](#2. K个一组翻转链表)
- [3. 全排列](#3. 全排列)
- [4. 轮转数组](#4. 轮转数组)
- [5. 合并有序链表](#5. 合并有序链表)
- [6. 下一个排列](#6. 下一个排列)
- [7. 最长公共前缀](#7. 最长公共前缀)
- [8. 二叉树的非递归前序遍历](#8. 二叉树的非递归前序遍历)
- 关于本博客内容的说明
一、面试问题
1. 创建索引
sql
-- 主键索引
ALTER TABLE users ADD PRIMARY KEY (id);
-- 唯一索引
ALTER TABLE table_name ADD UNIQUE (column);
ALTER TABLE table_name ADD UNIQUE (column1,column2);
-- 普通索引
ALTER TABLE table_name ADD INDEX index_name (column);
ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);
-- 全文索引
ALTER TABLE table_name ADD FULLTEXT (column);
2. 拦截器(Interceptor)和过滤器(Filter)的区别
- 作用位置不同
- 过滤器(Filter) :基于Servlet规范,工作在Web容器层(如Tomcat),对所有请求/响应进行预处理和后处理。
- 拦截器(Interceptor) :基于框架(如Spring MVC),工作在应用层,在控制器(Controller)方法前后执行。
- 控制粒度不同
- Filter:可拦截所有HTTP请求(静态资源、Servlet、JSP等),但无法获取具体的控制器和方法信息。
- Interceptor :可精确控制到某个Controller或方法,并能获取参数、返回值等上下文信息。
- 依赖框架不同
- Filter:是JavaEE标准,不依赖Spring等框架。
- Interceptor :通常依赖框架(如Spring的
HandlerInterceptor
)。
- 执行顺序
- Filter → 2. Interceptor → 3. Controller → 4. Interceptor → 5. Filter
- 常见用途
- Filter:全局编码设置、跨域处理(CORS)、日志记录、权限拦截(粗粒度)。
- Interceptor:登录校验、参数校验、审计日志(细粒度)。
一句话总结:
- Filter 是Web容器的"门卫",Interceptor是Spring的"管家",前者更底层,后者更灵活。
3. 为什么jwt令牌代替session?
-
无状态 & 可扩展性
- Session:依赖服务端存储(如Redis),集群环境下需共享Session,增加复杂度。
- JWT:令牌自带用户信息(签名加密),服务端无需存储,天然支持分布式。
-
跨域/跨服务支持
- Session:基于Cookie,受同源策略限制,跨域需额外配置(如CORS)。
- JWT :可放在HTTP Header(如
Authorization
),轻松实现跨域、微服务间认证。
-
性能优势
- Session:每次请求需查询服务端存储(如Redis),增加I/O开销。
- JWT:只需本地验签(如HS256/RSA),减少服务端压力。
-
移动端/API友好
- Session:移动端(APP)需额外适配Cookie机制。
- JWT:直接通过Header传递,适配RESTful API、APP、第三方调用。
-
安全性权衡
- Session:服务端可控(强制失效、续期)。
- JWT:需自行处理令牌过期(如Refresh Token)、无法主动废止(需短有效期+黑名单)。
总结 :
JWT适合分布式、无状态、跨平台 场景,Session更适合强控制、短会话需求。实际可结合使用(如JWT存用户ID,关键操作二次验证)。
4. 有一个100行的数据,和一个1万行的数据,写sql 的时候要注意什么?
性能优化重点
-
索引策略
- 对1万行表的关键查询字段建立索引
- 小表(100行)通常不需要索引
-
JOIN操作优化
sql-- 优先小表驱动大表 SELECT * FROM small_table s JOIN large_table l ON s.id = l.sid
-
查询范围控制
- 对大表使用WHERE条件尽早过滤数据
- 避免
SELECT *
,只查询必要字段
具体注意事项
-
执行计划分析
- 对1万行表查询使用EXPLAIN分析执行计划
- 关注是否使用了合适的索引
-
分页处理
sql-- 大表必须分页查询 SELECT * FROM large_table LIMIT 100 OFFSET 0
-
批量操作
- 对大表避免循环单条操作,使用批量INSERT/UPDATE
-
事务管理
- 大表操作使用合理的事务大小,避免长事务
-
内存考虑
- 1万行数据可能超出内存时,考虑流式处理
5. redis和memcache的区别
对比维度 | Redis | Memcached |
---|---|---|
数据结构 | 支持多种数据结构(String、Hash、List、Set、Sorted Set等) | 仅支持简单的 Key-Value 字符串 |
持久化 | 支持 RDB(快照)和 AOF(日志)持久化,数据可恢复 | 纯内存存储,重启后数据丢失 |
性能 | 单线程模型(避免锁竞争),适合复杂操作和高并发读 | 多线程模型,纯 KV 场景下吞吐量更高 |
集群支持 | 原生支持集群(Redis Cluster)、主从复制 | 无内置集群,依赖客户端分片(如一致性哈希) |
内存管理 | 支持 LRU 淘汰策略,可配置最大内存限制 | 固定内存分配,超出时自动淘汰旧数据 |
适用场景 | 缓存、会话存储、消息队列、排行榜、实时分析等复杂场景 | 简单的键值缓存,如 HTML 片段、数据库查询结果缓存 |
事务支持 | 支持简单事务(MULTI/EXEC)和 Lua 脚本 | 无事务支持 |
网络模型 | 单线程 Reactor 模式(6.0 后支持多线程 I/O) | 多线程处理请求 |
数据大小 | 适合存储中小型数据(Value 通常 ≤ 1MB) | 适合存储较大的 Value(默认支持 1MB,可调整) |
6. linux部分命令
-
统计一个文件中某个字符串出现次数
shellgrep -o "要搜索的字符串" 文件名 | wc -l
-
某个端口号被哪个进程占用
shellnetstat -tulnp | grep ":端口号" lsof -i :端口号
-
显示所有进程
shellps aux top
7. 飞书视频测试功能点
- 功能测试
- 基础功能:发起/加入视频会议、摄像头/麦克风开关、屏幕共享、会议录制。
- 权限控制:主持人权限(禁言、踢人)、参会者权限(发言、聊天)。
- UI交互:按钮状态、布局适配、横竖屏切换。
- 兼容性测试
- 设备:iOS/Android/Windows/macOS、不同分辨率。
- 浏览器:Chrome/Firefox/Safari/Edge(Web端)。
- 版本兼容:新旧版本飞书客户端互通。
- 性能测试
- 网络:弱网(3G/高延迟/丢包)下的视频流畅度。
- 负载:多人会议(50+/100+)时的CPU、内存占用。
- 稳定性:长时间会议(2h+)是否卡顿或崩溃。
- 安全测试
- 加密:视频流是否端到端加密。
- 防入侵:非法链接/密码爆破防护。
- 隐私:会议链接泄露后能否二次加入。
- 异常测试
- 断网恢复:网络中断后自动重连。
- 设备异常:摄像头/麦克风被占用时的提示。
- 冲突场景:来电/其他APP通知是否会中断会议。
8. 飞书视频性能保障
- 网络优化(最高优先级)
- 弱网适配:动态码率调整(如WebRTC的拥塞控制),确保高延迟/丢包下仍流畅。
- CDN加速:就近接入节点,降低跨国/跨运营商延迟。
- 协议优化:采用UDP(如QUIC)减少重传,提升实时性。
- 端侧性能
- 设备兼容:针对低端机型优化编解码(H.264/AV1)、降低CPU/内存占用。
- 功耗控制:智能降帧率/分辨率,避免过热耗电。
- 服务端负载
- 分布式架构:媒体服务器分区域部署,避免单点过载。
- 弹性扩缩容:自动扩容应对突发流量(如大型直播)。
- 关键指标监控
- QoS指标:帧率(≥15fps)、延迟(≤500ms)、卡顿率(<5%)。
- 告警机制:实时监控异常(如服务器CPU>80%),自动降级策略。
9. 在线协同文档的测试用例
- 基础功能测试
- 文档编辑与保存
- 新建文档:验证能否正确创建文本、表格、幻灯片等。
- 编辑内容:输入文本、插入图片、调整格式(字体、颜色、段落)。
- 自动保存:检查编辑后是否自动保存,断网恢复后数据是否同步。
- 多人实时协作
- 多人同时编辑:多个用户同时修改同一段落,检查冲突处理(如OT算法)。
- 光标位置显示:不同用户的编辑光标是否实时可见。
- 操作同步延迟 :修改后,其他用户能否在 1s 内 看到更新。
- 评论与批注
- 添加评论:@他人、回复评论、删除评论。
- 批注模式:不同权限用户能否查看/修改批注。
- 文档编辑与保存
- 权限与安全测试
- 访问权限
- 只读/可编辑/管理员:不同角色能否正确操作(如仅查看、可修改、可分享)。
- 链接分享:公开链接、指定人员访问、密码保护链接。
- 数据安全
- 内容加密:传输是否使用 HTTPS,存储是否加密。
- 防篡改:无权限用户尝试修改时是否被拒绝。
- 访问权限
- 性能测试
- 高并发编辑:100+ 用户同时编辑时,服务器是否稳定。
- 大文档加载:10MB+ 文档的打开速度(应 ≤3s)。
- 弱网环境:3G/高延迟下,操作是否正常同步。
- 兼容性测试
- 浏览器:Chrome/Firefox/Safari/Edge。
- 设备:PC/手机/平板,不同分辨率适配。
- 操作系统:Windows/macOS/iOS/Android。
- 异常场景测试
- 网络异常
- 断网恢复:编辑过程中断网,恢复后数据是否同步。
- 冲突处理:两人同时修改同一行,是否提示冲突或自动合并。
- 数据恢复
- 版本历史:能否回滚到任意历史版本。
- 误删恢复:删除文档后,能否在回收站恢复。
- 网络异常
10. 飞书低代码平台页面的性能保障工作
保障飞书低代码平台页面的性能需要从前端渲染 、后端服务 、数据层 、网络传输 和监控运维等多个角度协同优化。
- 前端性能优化
- 减少渲染负载
- 组件懒加载 :按需加载非首屏组件(如动态
import()
或React.lazy)。 - 虚拟列表(Virtualized List) :长列表渲染仅展示可视区域元素(如使用
react-window
)。 - DOM复用:避免重复渲染,合理使用React.memo或Vue的v-once。
- 组件懒加载 :按需加载非首屏组件(如动态
- 资源优化
- 代码拆分(Code Splitting):基于路由或功能拆包,减少首屏资源体积。
- 静态资源CDN加速:JS/CSS/图片等通过CDN分发,启用HTTP/2 + Brotli压缩。
- 图标与图片优化:使用SVG代替位图,懒加载非关键图片,WebP格式适配。
- 缓存策略
- 本地缓存:利用IndexedDB或LocalStorage缓存元数据(如表单配置)。
- Service Worker:实现离线可用性,加速重复访问。
- 减少渲染负载
- 后端服务优化
- 接口性能
- GraphQL替代REST:按需查询字段,减少数据传输量。
- 批量请求合并:多个独立API合并为单个请求(如Facebook的Batch API)。
- 接口缓存:高频读取数据(如组织架构)使用Redis缓存,设置合理TTL。
- 计算优化
- 预计算与预聚合:复杂报表数据提前计算,避免实时处理。
- 异步任务队列:耗时操作(如导出Excel)转为后台任务,通过WebSocket通知结果。
- 服务治理
- 自动扩缩容:基于CPU/内存指标动态调整K8s Pod副本数。
- 熔断与降级:依赖服务故障时降级返回缓存数据或简化逻辑(Hystrix/Sentinel)。
- 接口性能
- 数据层优化
- 数据库查询
- 索引优化:为低代码动态查询字段添加组合索引,避免全表扫描。
- 读写分离:查询走从库,写入走主库(如MySQL Group Replication)。
- 分库分表:大企业数据按租户ID或业务维度拆分。
- 实时同步
- 增量更新:通过Binlog或CDC(如Debezium)同步数据变更,避免全量拉取。
- OT/CRDT算法:文档协同场景使用操作转换或冲突-free 数据类型。
- 数据库查询
- 网络传输优化
- 协议升级:HTTP/3(QUIC)减少连接延迟,尤其适合移动端。
- 数据压缩:接口Payload使用Protocol Buffers或MessagePack。
- 边缘计算:动态内容通过边缘节点(如Cloudflare Workers)就近处理。
- 监控与持续优化
- 性能指标监控
- 前端埋点:采集FP/FCP/LCP等Web Vitals,监听长任务(Long Tasks)。
- 全链路追踪:通过OpenTelemetry追踪API调用链,定位慢请求。
- 日志分析:ELK或Prometheus + Grafana监控慢查询、异常错误。
- 性能测试
- 压力测试:模拟多租户并发操作(如JMeter),验证服务极限。
- A/B测试:对比不同优化策略(如缓存策略)的实际效果。
- 渐进式优化
- 性能评分卡:定期审计关键路径(如表单加载→提交全流程)。
- 代码分析工具:ESLint插件检测性能反模式(如未销毁的Event Listener)。
- 性能指标监控
- 低代码特定场景优化
- 动态表单渲染:预编译JSON Schema为高效渲染代码,避免运行时解析。
- 逻辑引擎性能:将可视化流程转换为WASM模块执行,提升计算效率。
- 沙箱隔离:用户自定义脚本运行在V8 Isolate或WebWorker中,避免阻塞主线程。
总结:性能保障体系
维度 | 关键措施 | 工具/技术示例 |
---|---|---|
前端 | 懒加载、虚拟列表、资源压缩 | React.lazy, Webpack, CDN |
后端 | 接口缓存、异步任务、自动扩缩容 | Redis, Kafka, Kubernetes HPA |
数据 | 索引优化、分库分表、增量同步 | MySQL索引, Debezium |
网络 | HTTP/3、边缘计算、数据压缩 | QUIC, Cloudflare Workers |
监控 | 全链路追踪、Web Vitals埋点 | OpenTelemetry, Sentry |
二、SQL
1. 查出表中学生成绩最好的学生信息
sql
SELECT * FROM students ORDER BY score DESC LIMIT 1;
SELECT * FROM students WHERE score = (SELECT MAX(score) FROM students);
2. 2020/12/1 10:28:31这种数据按照小时统计每个不同小时出现数据个数
sql
SELECT
DATE_FORMAT(time_column, '%Y-%m-%d %H:00:00') AS hour,
COUNT(*) AS count
FROM
your_table
WHERE
time_column BETWEEN '2020-12-01' AND '2020-12-02' -- 可选时间范围
GROUP BY
DATE_FORMAT(time_column, '%Y-%m-%d %H')
ORDER BY
hour;
3. 查询姓王 的同学的个数、年龄第二大的同学
sql
SELECT COUNT(*) AS 王姓同学人数 FROM students WHERE name LIKE '王%';
SELECT * FROM students WHERE age < (SELECT MAX(age) FROM students) ORDER BY age DESC LIMIT 1;
三、手撕算法
1. 二分搜索二维数组
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int i = 0, j = matrix[0].length - 1;
while (i <= matrix.length - 1 && j >= 0) {
if (matrix[i][j] == target) {
return true;
}
if (matrix[i][j] < target) {
i++;
continue;
}
if (matrix[i][j] > target) {
j--;
continue;
}
}
return false;
}
}
2. K个一组翻转链表
java
// 给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null) {
return null;
}
ListNode pre = head, index = head.next; // 双指针操纵反转
int m = 0;
while (++m < k && index != null) {
pre.next = index.next;
index.next = head;
head = index;
index = pre.next;
}
if (m < k) {
return reverseKGroup(head, m);
}
pre.next = reverseKGroup(index, k);
return head;
}
}
3. 全排列
java
class Solution {
public List<List<Integer>> permute(int[] nums) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<>();
if (len == 0) {
return res;
}
List<Integer> path = new ArrayList<>();
boolean[] used = new boolean[len];
dfs(nums, len, 0, res, path, used);
return res;
}
private void dfs(int[] nums, int len, int depth, List<List<Integer>> res, List<Integer> path,
boolean[] used) {
if (depth == len) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < len; i++) {
if (!used[i]) {
path.add(nums[i]);
used[i] = true;
dfs(nums, len, depth + 1, res, path, used);
used[i] = false;
path.remove(path.size() - 1);
}
}
}
}
4. 轮转数组
java
class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k = k % n;
// 0 - n-1 翻转
reverse(nums, 0, n - 1);
// 0 - k-1 翻转
reverse(nums, 0, k - 1);
// k - n-1 翻转
reverse(nums, k, n - 1);
}
public void reverse(int[] nums, int start, int end) {
int tmp;
while (start < end) {
tmp = nums[start];
nums[start] = nums[end];
nums[end] = tmp;
start++;
end--;
}
}
}
5. 合并有序链表
java
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
if (list1.val < list2.val) {
list1.next = mergeTwoLists(list1.next, list2);
return list1;
} else {
list2.next = mergeTwoLists(list1, list2.next);
return list2;
}
}
}
6. 下一个排列
java
class Solution {
public void nextPermutation(int[] nums) {
// 1.从后向前查找第一个顺序对(i, i + 1),满足a[i] < a[i + 1],a[i]即为较小数,
// 且[i + 1, n]为下降序列
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
int j = nums.length - 1;
// 2. 在区间[i + 1, n]中从后向前查找第一个元素j,满足a[i] < a[j]
while (j >= 0 && nums[i] >= nums[j]) {
j--;
}
swap(nums, i, j);
}
// 如果在步骤 1 找不到顺序对,说明当前序列已经是一个降序序列,
// 即最大的序列,我们直接跳过步骤 2 执行步骤 3,即可得到最小的升序序列。
reverse(nums, i + 1);
}
public void swap(int[] nums, int start, int end) {
int tmp = nums[start];
nums[start] = nums[end];
nums[end] = tmp;
}
public void reverse(int[] nums, int start) {
int end = nums.length - 1;
while (start < end) {
swap(nums, start, end);
start++;
end--;
}
}
}
7. 最长公共前缀
java
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs.length == 0) {
return "";
}
String res = strs[0];
for (int i = 1; i < strs.length; i++) {
int j = 0;
for (; j < res.length() && j < strs[i].length(); j++) {
if (res.charAt(j) != strs[i].charAt(j)) {
break;
}
}
res = res.substring(0, j);
if (res.equals("")) {
return res;
}
}
return res;
}
}
8. 二叉树的非递归前序遍历
java
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Deque<TreeNode> stack = new LinkedList<>();
TreeNode node = root;
while (!stack.isEmpty() || node != null) {
while (node != null) {
res.add(node.val);
stack.push(node);
node = node.left;
}
node = stack.pop();
node = node.right;
}
return res;
}
}
关于本博客内容的说明
-
内容来源
本文整理的时间有限,部分答案是结合DeepSeek的技术解读与个人理解综合而成。需要说明的是,这些问题在实际面试中(至少在我的经历里)大多并未被直接问到------毕竟面试内容往往取决于具体岗位需求和面试官风格。
-
算法题部分
手撕代码环节中,我原以为面试官会考察第二道题目(LeetCode困难题),因为从该题的评论区来看,包括字节在内的多家大厂都曾将其作为考题。
另外,我提供的示例代码时间复杂度可能并非最优解,这里主要是记录当前算法水平下我能快速实现的思路。毕竟面试中,清晰的逻辑表达和正确性通常比极致优化更重要(当然,如果能兼顾就更好了)。