今天看学长面试了西安SAP公司,写一篇面经记录
这里就省略自我介绍,直接上干货
Q1:虚拟内存管理是什么?(计算机操作系统)
A1:
虚拟内存管理是现代操作系统(如 Linux、Windows)的核心机制之一,它通过抽象物理内存,为每个进程提供一个连续、隔离、大小统一的虚拟地址空间,从而解决内存碎片、安全隔离、多任务并发等问题。
为什么需要虚拟内存?
如果没有虚拟内存,程序直接操作物理地址会面临:
- ❌ 内存碎片:分配/释放后物理内存不连续,大对象无法分配
- ❌ 进程互相干扰:一个进程写错地址可能破坏其他进程数据
- ❌ 无法运行大于物理内存的程序
- ❌ 安全风险:任意进程可读写内核或其他进程内存
虚拟内存解决了这些问题。
核心思想:地址空间隔离 + 按需映射
每个进程都拥有自己独立的 虚拟地址空间(例如 64 位 Linux 下通常是 128TB),但这些地址并不直接对应物理内存,而是通过 页表(Page Table) 由 CPU 的 MMU(Memory Management Unit) 动态映射到物理内存或磁盘。
✅ 对进程来说:内存是“连续”的
✅ 对 OS 来说:物理内存可以“离散”分配
关键机制详解
1. 分页(Paging)
- 虚拟内存和物理内存都被划分为固定大小的 页(Page) ,通常为 4KB(也有 2MB、1GB 的大页)
- 虚拟页(Virtual Page) → 通过页表 → 物理页帧(Physical Frame)
2. 页表(Page Table)
- 每个进程有一个页表,记录虚拟页到物理页的映射关系
- 页表本身也存储在内存中,由 CR3 寄存器指向当前进程的页目录基址
- 为提高效率,CPU 提供 TLB(Translation Lookaside Buffer) 缓存最近的映射
3. 缺页中断(Page Fault)
当进程访问一个尚未加载到物理内存的虚拟页时:
-
CPU 触发 缺页异常(Page Fault)
-
操作系统介入:
-
若该页在磁盘(swap 或可执行文件),则将其加载到物理内存
-
更新页表,建立映射
-
重新执行原指令
💡 这就是“按需分页(Demand Paging)”——程序启动快,只加载用到的部分
-
4. 交换(Swapping)
- 当物理内存不足时,OS 会将不活跃的页写入磁盘的 Swap 分区
- 需要时再换入(Swap In)
- ⚠️ 频繁 Swap 会导致系统卡顿(“抖动 Thrashing”)
5. 内存保护与权限
页表项(PTE)包含权限位:
- R/W:是否可写
- U/S:用户态/内核态
- NX(No-eXecute) :禁止执行(防缓冲区溢出攻击)
🔒 Java 程序若越界访问(如 native 代码 bug),会触发 Segmentation Fault(SIGSEGV)
总结(面试回答模板)
“虚拟内存是操作系统通过 MMU 和页表,为每个进程提供独立、连续的地址空间的机制。它基于分页实现,利用缺页中断按需加载数据,并通过权限位保障安全。对 Java 应用而言,JVM 堆只是虚拟地址空间的一部分,实际物理内存按需分配,这解释了为何
-Xmx不等于实际内存占用。在高并发或容器化场景中,理解虚拟内存有助于排查 OOM、性能抖动等问题。”
Q2:线程和进程的区别是什么?(计算机操作系统)
A2:
根本定义
| 概念 | 定义 |
|---|---|
| 进程(Process) | 操作系统分配资源的基本单位。一个正在运行的程序实例,拥有独立的内存空间(虚拟地址空间) 、文件描述符、环境变量等。 |
| 线程(Thread) | CPU 调度和执行的基本单位。属于某个进程,多个线程共享所属进程的资源(如堆内存、打开的文件),但拥有独立的栈、寄存器状态、程序计数器。 |
✅ 一句话总结:
进程是“资源容器”,线程是“执行单元” 。
一个进程至少包含一个线程(主线程),也可以包含多个线程(多线程)。
核心区别对比表
| 维度 | 进程(Process) | 线程(Thread) |
|---|---|---|
| 内存空间 | ✅ 完全隔离(独立虚拟地址空间) | ❌ 共享所属进程的堆、代码段、数据段 |
| 栈 | 每个进程有主线程栈 | ✅ 每个线程有独立的栈(用于局部变量、方法调用) |
| 创建/销毁开销 | ⚠️ 大(需分配内存、建立页表、加载镜像等) | ✅ 小(只需分配栈 + TCB) |
| 通信方式 | IPC:管道、消息队列、共享内存、Socket 等 | ✅ 直接读写共享内存(如 Java 的 Heap 对象) |
| 同步复杂度 | 低(天然隔离) | ⚠️ 高(需锁、volatile、CAS 等保证线程安全) |
| 健壮性 | ✅ 一个进程崩溃不影响其他进程 | ❌ 一个线程崩溃可能导致整个进程退出 |
| 切换成本 | ⚠️ 高(需切换页表、TLB 刷新等) | ✅ 低(只需保存/恢复寄存器、栈指针) |
| 典型例子 | Chrome 每个标签页是一个进程 | Java 应用中的 main 线程、Tomcat 的请求处理线程池 |
常见误区澄清
| 误区 | 正确理解 |
|---|---|
| “线程比进程快,所以应该多用线程” | 线程虽轻量,但并发带来复杂性;合理使用线程池,避免无限制创建 |
| “每个线程都有自己的堆” | ❌ 堆是进程级别的!线程只有自己的栈 |
| “多进程不能共享内存” | 可通过 共享内存(Shared Memory) IPC 实现,但复杂且需同步 |
| “Java 线程 = 操作系统线程” | 在主流 JVM(HotSpot)中,是 1:1 映射(绿色线程已淘汰) |
面试回答模板
“进程是操作系统资源分配的基本单位,拥有独立的内存空间;线程是 CPU 调度的基本单位,属于进程,多个线程共享进程的堆、文件描述符等资源,但各自拥有独立的栈。
在 Java 中,我们通常在一个进程中创建多个线程来处理并发请求,这提高了资源利用率,但也引入了线程安全问题,需要通过锁、原子类或无共享设计来保证正确性。
相比之下,多进程天然隔离,适合微服务架构,但通信和资源开销更大。”
Q3:HTTP和HTTPS区别是什么?默认端口是什么?(计算机网络)
A3:
核心区别对比表
| 特性 | HTTP(HyperText Transfer Protocol) | HTTPS(HTTP Secure) |
|---|---|---|
| 安全性 | ❌ 明文传输,数据可被窃听、篡改、伪造 | ✅ 加密传输(TLS/SSL),防窃听、防篡改、防冒充 |
| 协议栈位置 | 应用层(直接跑在 TCP 上) | 应用层(HTTP + TLS/SSL 层 → 再跑在 TCP 上) |
| 加密方式 | 无 | 混合加密: • 非对称加密(RSA/ECC)交换密钥 • 对称加密(AES)加密数据 |
| 身份认证 | 无 | ✅ 通过数字证书验证服务器身份(可选客户端证书) |
| 默认端口 | 80 | 443 |
| URL 前缀 | http:// | https:// |
| 性能开销 | 低(无加解密) | 略高(首次握手有 RTT 开销,但现代优化已大幅降低) |
| SEO / 浏览器体验 | 被标记为“不安全” | ✅ 被浏览器信任,利于 SEO |
HTTPS 的工作原理(简化版)
-
客户端发起请求
https://example.com→ 连接服务器 443 端口 -
TLS 握手(关键!)
- 客户端发送支持的加密套件
- 服务器返回 数字证书(含公钥 + CA 签名)
- 客户端验证证书合法性(是否过期、域名匹配、CA 可信)
- 双方协商出一个 会话密钥(Session Key)
- 后续通信使用该密钥进行对称加密
-
加密传输 HTTP 数据
所有请求/响应(Header + Body)均被加密
🔐 核心价值:即使数据被中间人截获,也无法解密或篡改(否则 MAC 校验失败)。
默认端口详解
| 协议 | 默认端口 | 说明 |
|---|---|---|
| HTTP | 80 | 浏览器访问 http://baidu.com 时自动连接 80 端口 |
| HTTPS | 443 | 浏览器访问 https://baidu.com 时自动连接 443 端口 |
💡 注意:
- 端口号是约定俗成的,实际可自定义(如
https://api.example.com:8443)- 但在生产环境中,必须使用 443 才能获得浏览器信任(否则会提示“不安全”)
常见误区澄清
| 误区 | 正确理解 |
|---|---|
| “HTTPS 只是加密,不影响功能” | ❌ HTTPS 还提供身份认证和完整性校验 |
| “用了 HTTPS 就绝对安全” | ❌ 仍需防范 XSS、CSRF、逻辑漏洞等 |
| “HTTP/2 必须用 HTTPS” | ✅ 在浏览器中是强制的(但 HTTP/2 协议本身不强制) |
| “端口 80 和 443 可以互换” | ❌ 浏览器和 CDN 默认行为依赖标准端口 |
面试回答模板
“HTTP 是明文传输的超文本协议,默认端口 80;HTTPS 是在 HTTP 和 TCP 之间加入 TLS/SSL 加密层的安全协议,默认端口 443。
HTTPS 通过数字证书实现服务器身份认证,并使用混合加密保证数据机密性和完整性。
在实际开发中,所有涉及用户隐私或敏感操作的接口都必须使用 HTTPS,同时要注意反向代理下的协议传递和证书管理。”
Q4:讲一下ssh协议?(计算机网络)
A4:
面试回答模板
“SSH 是一种基于 TCP 的加密网络协议,默认端口 22,用于安全远程登录和文件传输。它通过 Diffie-Hellman 密钥交换建立加密通道,并支持公钥认证实现免密登录。相比 Telnet/FTP,SSH 能有效防止窃听、篡改和中间人攻击。在实际开发中,我们使用 SSH 公钥实现 CI/CD 自动化部署,并通过端口转发安全访问内网服务。”
SSH 协议架构(分层设计)
SSH 协议栈分为三层:
| 层级 | 功能 | 关键技术 |
|---|---|---|
| 1. 传输层(Transport Layer) | 提供加密、压缩、完整性校验 | • Diffie-Hellman 密钥交换 • 对称加密(AES, ChaCha20) • HMAC 消息认证 |
| 2. 用户认证层(User Authentication Layer) | 验证客户端身份 | • 密码认证 • 公钥认证(推荐!) • 键盘交互式认证 |
| 3. 连接层(Connection Layer) | 多路复用、端口转发、子系统 | • 多个逻辑通道(shell、sftp、端口转发) • 支持 X11 转发、代理跳转 |
🔑 整个过程建立在 TCP 之上,默认端口:22
SSH 工作流程(以公钥认证为例)
这是最安全、最常用的登录方式(你在简历中提到“自动化部署”,很可能就用它):
步骤 1:建立加密通道(传输层)
- 客户端连接服务器 22 端口
- 双方协商加密算法(如
aes256-ctr+hmac-sha2-256) - 通过 Diffie-Hellman 密钥交换 生成共享会话密钥(即使被监听也无法推导)
步骤 2:服务器身份验证
-
服务器发送其 公钥(Host Key)
-
客户端检查
~/.ssh/known_hosts是否已信任该公钥- 若首次连接 → 提示 “Are you sure you want to continue connecting?”
- 若公钥变更 → 警告 “WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!”(防中间人攻击)
步骤 3:客户端身份认证(公钥方式)
- 客户端声明:“我想用
id_rsa.pub登录” - 服务器在
~/.ssh/authorized_keys中查找该公钥 - 服务器生成一个随机 challenge,用公钥加密后发给客户端
- 客户端用私钥解密并返回响应
- 服务器验证成功 → 认证通过
✅ 优势:私钥永不离开客户端,比密码更安全,支持免密登录。
SSH 核心功能(不止是远程登录!)
| 功能 | 命令示例 | 应用场景 |
|---|---|---|
| 远程 Shell | ssh user@host | 登录服务器执行命令 |
| 安全文件传输 | scp file user@host:/path sftp user@host | 替代 FTP,加密传文件 |
| 端口转发(隧道) | ssh -L 8080:localhost:8080 user@jump-host | 内网穿透、安全访问数据库 |
| 代理跳转(Jump Host) | ssh -J jump-user@jump-host target-user@target-host | 通过堡垒机访问内网机器 |
| X11 转发 | ssh -X user@host | 远程运行 GUI 程序(如性能分析工具) |
💡 你在自动化部署中可能用到:
# 免密执行远程命令(配合公钥) ssh deploy@prod-server "cd /app && ./restart.sh"
五、SSH 与 HTTPS 的区别(常被混淆)
| 特性 | SSH | HTTPS |
|---|---|---|
| 目的 | 安全远程管理/文件传输 | 安全 Web 浏览/API 调用 |
| 默认端口 | 22 | 443 |
| 认证方式 | 主机公钥 + 用户公钥/密码 | CA 签发的服务器证书 |
| 协议模型 | 客户端-服务器(双向可认证) | 浏览器-服务器(通常单向认证) |
| 数据格式 | 二进制协议(非文本) | 基于 HTTP(文本/JSON) |
| 典型工具 | OpenSSH, PuTTY | 浏览器, curl, RestTemplate |
📌 关键差异:
HTTPS 依赖第三方 CA 信任链,而 SSH 依赖首次连接信任(Trust on First Use, TOFU) 。
Q5:讲一下数据库事务的四大特性?(数据库)
A5:
面试回答模板
“数据库事务的 ACID 四大特性是:
原子性(Atomicity)确保操作全成功或全失败;
一致性(Consistency)保证数据始终满足业务规则;
隔离性(Isolation)通过隔离级别控制并发副作用;
持久性(Durability)确保提交后的数据永不丢失。
在 MySQL InnoDB 中,原子性靠 Undo Log,持久性靠 Redo Log,隔离性靠 MVCC 和锁机制。而在分布式场景下,我们往往牺牲强一致性,采用最终一致性方案来保证系统可用性。”
ACID 四大特性详解
| 特性 | 全称 | 中文含义 | 核心作用 | 举例说明 |
|---|---|---|---|---|
| A | Atomicity | 原子性 | 事务中的所有操作要么全部成功,要么全部失败回滚 | 转账:A 扣 100 元 + B 加 100 元 必须同时成功或同时失败 |
| C | Consistency | 一致性 | 事务执行前后,数据库必须从一个合法状态转移到另一个合法状态(满足约束、触发器、业务规则) | 账户余额不能为负;外键约束不能被破坏 |
| I | Isolation | 隔离性 | 多个并发事务之间互不干扰,如同串行执行 | A 查询余额时,不能看到 B 正在转账的中间状态 |
| D | Durability | 持久性 | 一旦事务提交,其结果将永久保存,即使系统崩溃也不会丢失 | 提交后,数据已写入磁盘(或 WAL 日志),重启后依然存在 |
原子性(Atomicity)
-
实现机制:通过 Undo Log(回滚日志) 实现。
-
关键点:
- 如果事务中途失败,数据库利用 Undo Log 将已修改的数据恢复到事务开始前的状态。
- 在 MySQL InnoDB 中,每条记录的旧值会被记录在 Undo Log 中。
-
Java 场景:
@Transactional public void transfer(Long fromId, Long toId, BigDecimal amount) { accountDao.decrease(fromId, amount); // 扣款 accountDao.increase(toId, amount); // 加款 // 若 increase 抛异常,decrease 也会回滚 }
一致性(Consistency)
-
注意:一致性是目的,而原子性、隔离性、持久性是手段。
-
数据库本身通过约束(主键、唯一索引、外键、Check)保证基础一致性。
-
业务一致性需由应用层保证(如“库存不能超卖”)。
-
例子:
- 表中有
CHECK (balance >= 0),则任何事务都不能使余额为负。 - 如果你转账 100 元但只扣了 A 的钱没加 B 的钱,就违反了一致性(但原子性会阻止这种情况)。
- 表中有
✅ 一致性 = 原子性 + 隔离性 + 持久性 + 应用逻辑正确性
隔离性(Isolation)
-
问题背景:多个事务并发执行可能引发:
- 脏读(Dirty Read) :读到未提交的数据
- 不可重复读(Non-Repeatable Read) :同一事务内多次读取同一数据,结果不同
- 幻读(Phantom Read) :同一查询条件,两次结果行数不同(新增/删除)
-
SQL 标准定义了 4 种隔离级别:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ 允许 | ✅ | ✅ | 性能最高,几乎不用 |
| READ COMMITTED(Oracle 默认) | ❌ 禁止 | ✅ 允许 | ✅ | 每次读取都生成新快照 |
| REPEATABLE READ(MySQL InnoDB 默认) | ❌ | ❌ | ⚠️ InnoDB 通过 MVCC + Gap Lock 解决 | 保证同一事务内多次读一致 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 完全串行化,并发性能最差 |
四、持久性(Durability)
-
实现机制:通过 Redo Log(重做日志) + WAL(Write-Ahead Logging) 。
-
关键流程(以 MySQL InnoDB 为例):
- 事务提交时,先将 Redo Log 写入磁盘(顺序 I/O,快)
- 后台线程异步将数据页刷入磁盘
- 即使 crash,重启后可通过 Redo Log 恢复已提交事务
-
保障:只要事务返回“提交成功”,数据就不会丢失(即使断电)。
💡 注意:若磁盘损坏或日志未刷盘(如
innodb_flush_log_at_trx_commit=2),仍可能丢数据。
ACID 在分布式系统中的挑战
在微服务架构中(如你的简历项目),单数据库 ACID 很难跨服务保证,此时常用:
- 最终一致性(通过消息队列补偿)
- Saga 模式(长事务拆解 + 补偿事务)
- TCC(Try-Confirm-Cancel)
- Seata 等分布式事务框架(AT 模式模拟本地事务)
例如:订单服务创建订单 + 库存服务扣减库存 → 无法用
@Transactional跨两个数据库。
Q6:SQL语言中左右连接和内连接是什么?(数据库)
A6:
面试回答模板
“SQL 中的内连接(INNER JOIN)只返回两表匹配的记录;左连接(LEFT JOIN)返回左表全部记录,右表无匹配时补 NULL;右连接(RIGHT JOIN)则相反。
在实际开发中,我常用 LEFT JOIN 查询主表全量数据(如所有用户及其行为),用 INNER JOIN 获取关联成功的数据(如有效订单)。需要注意的是,LEFT JOIN 中 ON 和 WHERE 的作用不同,错误使用可能导致意外过滤掉 NULL 行。”
核心概念图解(以两个表为例)
假设有两张表:
users 表(用户)
| user_id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
orders 表(订单)
| order_id | user_id | product |
|---|---|---|
| 101 | 1 | Laptop |
| 102 | 1 | Mouse |
| 103 | 4 | Keyboard |
三种连接详解
1. 内连接(INNER JOIN)
-
含义:只返回两个表中都存在匹配记录的行。
-
关键字:
INNER JOIN或简写为JOIN -
SQL 示例:
SELECT u.name, o.product FROM users u INNER JOIN orders o ON u.user_id = o.user_id; -
结果:
name product Alice Laptop Alice Mouse
✅ 特点:
- 不包含任何“孤儿”数据(如 Charlie 没订单,Keyboard 的 user_id=4 无效)
- 最常用,性能通常最好(可利用索引高效过滤)
2. 左连接(LEFT JOIN / LEFT OUTER JOIN)
-
含义:返回左表(FROM 后的表)的所有记录,即使右表没有匹配项;右表无匹配时,字段为
NULL。 -
关键字:
LEFT JOIN或LEFT OUTER JOIN -
SQL 示例:
SELECT u.name, o.product FROM users u LEFT JOIN orders o ON u.user_id = o.user_id; -
结果:
name product Alice Laptop Alice Mouse Bob NULL Charlie NULL
✅ 典型场景:
- 查询“所有用户及其订单(包括未下单用户)”
- 统计每个用户的操作次数(即使为 0)
3. 右连接(RIGHT JOIN / RIGHT OUTER JOIN)
-
含义:返回右表(JOIN 后的表)的所有记录,即使左表没有匹配项;左表无匹配时,字段为
NULL。 -
关键字:
RIGHT JOIN或RIGHT OUTER JOIN -
SQL 示例:
SELECT u.name, o.product FROM users u RIGHT JOIN orders o ON u.user_id = o.user_id; -
结果:
name product Alice Laptop Alice Mouse NULL Keyboard
⚠️ 注意:
RIGHT JOIN可通过交换表顺序 +LEFT JOIN实现,实际开发中很少使用(可读性差)上例等价于:
SELECT u.name, o.product FROM orders o LEFT JOIN users u ON u.user_id = o.user_id;
对比总结表
| 连接类型 | 返回哪些行? | 是否包含 NULL? | 常用程度 |
|---|---|---|---|
| INNER JOIN | 两表都有匹配的行 | ❌ 不会 | ⭐⭐⭐⭐⭐ |
| LEFT JOIN | 左表全部 + 右表匹配部分(无匹配则 NULL) | ✅ 右表字段可能为 NULL | ⭐⭐⭐⭐ |
| RIGHT JOIN | 右表全部 + 左表匹配部分(无匹配则 NULL) | ✅ 左表字段可能为 NULL | ⭐ |
📌 记忆口诀:
- INNER → “交集”
- LEFT → “左表全保留”
- RIGHT → “右表全保留”(但建议用 LEFT 替代)
Q7:SQL中where和on的区别?(数据库)
A7:
面试回答模板
“
ON是连接条件,用于定义表之间如何关联,作用于 JOIN 执行过程中;WHERE是筛选条件,作用于最终结果集。
在 LEFT JOIN 中,若将右表的过滤条件写在 WHERE 中,会导致左表中无匹配的行被意外过滤,失去 LEFT JOIN 的意义。
因此,当我们需要保留主表全量数据时(如所有用户),应把关联表的过滤条件放在 ON 子句中。”
详细对比(以 LEFT JOIN 为例)
users
| user_id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
orders
| order_id | user_id | amount |
|---|---|---|
| 101 | 1 | 100 |
| 102 | 1 | 200 |
| 103 | 3 | 50 |
✅ 场景 1:用 ON 过滤右表(正确做法)
SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o
ON u.user_id = o.user_id AND o.amount > 150; -- ON 中加条件
执行过程:
- 先根据
ON条件 构建连接关系:只将amount > 150的订单与用户关联 - 再保留左表(users)所有行
结果:
| name | amount |
|---|---|
| Alice | 200 |
| Bob | NULL |
✅ 语义:查询所有用户,以及他们金额大于 150 的订单
❌ 场景 2:用 WHERE 过滤右表(常见错误!)
SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o
ON u.user_id = o.user_id
WHERE o.amount > 150; -- WHERE 中加条件
执行过程:
- 先完成完整 LEFT JOIN(包含所有订单)
- 再用
WHERE过滤最终结果 →o.amount > 150 - 由于 Bob 的
o.amount是NULL,NULL > 150为UNKNOWN(即 false),Bob 被过滤掉!
结果:
| name | amount |
|---|---|
| Alice | 200 |
❌ 问题:这实际上变成了 INNER JOIN!失去了 LEFT JOIN “保留左表全部”的语义。
对比总结表
| 特性 | ON子句 | WHERE子句 |
|---|---|---|
| 作用阶段 | 连接过程中(构建临时结果集时) | 连接完成后(对最终结果过滤) |
| 适用位置 | 只能在 JOIN 后使用 | 可用于任何 SELECT 查询 |
| 对 LEFT JOIN 的影响 | 不会减少左表行数 | 可能过滤掉左表行(当右表字段为 NULL 时) |
| 性能 | 通常更优(提前减少连接数据量) | 可能处理更多中间数据 |
| 语义重点 | “如何关联表” | “要哪些结果” |
不同 JOIN 类型下的行为差异
| JOIN 类型 | ON中过滤右表 | WHERE中过滤右表 |
|---|---|---|
| INNER JOIN | 等价于 WHERE(结果相同) | 等价于 ON |
| LEFT JOIN | 保留左表全部,右表按条件匹配 | 可能丢失左表行(变成 INNER JOIN) |
| RIGHT JOIN | 保留右表全部,左表按条件匹配 | 可能丢失右表行 |
| FULL JOIN | 保留两表全部,按条件匹配 | 可能丢失任一方的行 |
💡 黄金法则:
- 如果你想保留主表的所有行(如所有用户、所有岗位),过滤条件应写在
ON中- 如果你只想获取满足条件的最终结果(不管是否保留主表),用
WHERE
Q8:HTTP协议中的常用状态码有哪些?
A8:3.1 HTTP 常见面试题 | 小林coding | Java面试学习
Q9:Linux的常用命令?如何查看磁盘使用情况?把a文件的内容拷贝到b中如何操作?
A9:
全局查看:整个系统的磁盘使用情况
✅ df(disk free)—— 查看挂载点的磁盘空间
df -h
-
-h:human-readable(以 GB/MB 显示,而不是 KB) -
输出示例:
Filesystem Size Used Avail Use% Mounted on /dev/nvme0n1p2 50G 30G 18G 63% / /dev/nvme0n1p1 512M 7.8M 504M 2% /boot tmpfs 16G 0 16G 0% /run/user/1000 -
重点关注:
Use%列:超过 80% 需警惕,100% 会导致应用写文件失败(如 Tomcat 日志、MySQL binlog)Mounted on:确认是哪个分区(如/、/var、/data)
💡 Java 场景:
若/分区快满了,可能是:
- 应用日志未轮转(
catalina.out暴涨)- Core dump 文件(JVM Crash 生成)
- Docker 镜像/容器日志堆积
定位大目录:哪个目录占空间最多?
✅ du(disk usage)—— 查看目录/文件的磁盘占用
# 查看当前目录下各子目录大小(排序后取前10)
du -h --max-depth=1 | sort -hr | head -10
# 查看 /var 目录下最大的子目录
sudo du -h /var --max-depth=1 | sort -hr
-h:易读格式--max-depth=1:只统计一级子目录(避免递归太深)sort -hr:按人类可读数字倒序排列(需 GNU coreutils)
📌 典型问题定位:
# 发现 /var/log 占了 20G sudo du -h /var/log --max-depth=1 # → 发现 catalina.out 有 18G! ls -lh /var/log/tomcat/catalina.out
快速找出最大的文件(尤其是日志)
✅ 使用 find + ls 组合
# 查找 /var 下大于 100MB 的文件,按大小排序
sudo find /var -type f -size +100M -exec ls -lh {} ; | awk '{ print $5 ": " $9 }' | sort -hr
✅ 或用 ncdu(推荐!交互式工具)
# 安装(Ubuntu/Debian)
sudo apt install ncdu
# 使用
ncdu / # 交互式浏览,方向键导航,d 删除(谨慎!)
- 优势:图形化、可钻取、支持删除(按
d) - 适合:快速定位“罪魁祸首”文件
| 需求 | 推荐命令 |
|---|---|
| 标准复制(覆盖) | cp a b |
| 仅复制内容(不保留元数据) | cat a > b |
| 追加内容 | cat a >> b |
💡 记住:
- 想保留原文件 → 用
cp- 只关心内容 → 用
cat >- 要追加 → 用
cat >>
Q10:简要介绍redis数据结构?(Redis)
A10:
| 数据结构 | 中文名 | 特点 | 典型应用场景 |
|---|---|---|---|
| String | 字符串 | 最基本类型,可存文本、数字、二进制(如图片) • 支持原子自增(INCR) • 可设置过期时间 | • 缓存对象(JSON 序列化) • 计数器(如接口限流) • 分布式锁(SET key value NX PX) |
| Hash | 哈希表 | 类似 Java 的 Map<String, String> • 存储对象字段(field-value) • 节省内存(相比多个 String) | • 用户信息(id → {name, age, email}) • 商品详情 |
| List | 列表 | 有序、可重复的字符串列表 • 支持从两端插入/弹出(LPUSH/RPOP) • 底层用 ziplist 或 linkedlist | • 消息队列(简单场景) • 最新 N 条动态(如朋友圈 feed) |
| Set | 集合 | 无序、唯一的字符串集合 • 支持交集、并集、差集运算 | • 标签系统(用户兴趣标签) • 抽奖(SRANDMEMBER) • 好友关系去重 |
| Sorted Set (ZSet) | 有序集合 | 每个成员带一个 score(分数) • 按 score 自动排序 • 支持范围查询(ZRANGEBYSCORE) | • 排行榜(如游戏积分) • 延时队列(score = 执行时间戳) • 带权重的 Top-K |
🧩 补充:Redis 7+ 新增的实用结构
| 结构 | 用途 |
|---|---|
| Bitmaps | 位图,用于统计(如日活用户) |
| HyperLogLog | 超低内存基数估算(如 UV 统计) |
| Geospatial | 地理位置(基于 Sorted Set 实现) |
| Streams | 高可靠消息队列(类似 Kafka 简化版) |
Q11:mcp是什么?(agent开发)
A11:
“MCP(Model Context Protocol)是一个新兴的开源标准,旨在为大语言模型提供统一、安全的外部工具调用接口。它通过标准化工具描述和基于 JSON-RPC 的通信机制,让不同模型能无缝集成数据库、API、文件系统等能力。相比 OpenAI Function Calling 或 LangChain Tools,MCP 的优势在于跨平台和开放生态,有望成为 AI Agent 时代的‘通用工具总线’。”
Model Context Protocol(模型上下文协议)
这是由 Anthropic、Databricks、Cohere、LlamaIndex 等公司于 2024 年底联合提出的一个新兴开源标准,旨在让 AI 模型(尤其是 LLM)能够安全、结构化地调用外部工具(Tools)和访问上下文数据。
📌 简单说:MCP 是让大模型“连接世界”的通用接口协议,类似于当年的 HTTP 之于 Web。
一、MCP 解决了什么问题?
在 MCP 出现前,每个 AI 应用(如 Copilot、Agent 系统)都用自己的方式集成工具(如数据库、API、代码解释器),导致:
- 🔧 工具无法复用:一个日历插件只能用于某一家模型
- 🛡️ 安全风险高:权限控制、输入校验各不相同
- 🧩 开发效率低:开发者要为不同平台重复造轮子
MCP 的目标:定义一套统一的通信协议,让任何支持 MCP 的模型都能调用任何 MCP 兼容的工具。
二、MCP 核心思想
-
标准化工具描述
工具通过 JSON Schema 声明自己的能力、参数、权限要求{ "name": "get_weather", "description": "获取指定城市的天气", "parameters": { "type": "object", "properties": { "city": { "type": "string" } } } } -
基于 LSP(Language Server Protocol)扩展
MCP 复用微软的 LSP 架构,使用 JSON-RPC over stdio/WebSocket 通信,天然支持 IDE、CLI、Web 等多种客户端。 -
上下文感知(Context Awareness)
模型可请求“当前用户有哪些可用工具”,系统动态返回授权后的工具列表。 -
安全沙箱
所有工具调用需经过 MCP 服务器鉴权,防止模型越权操作。
三、MCP 典型架构
[ LLM (如 Claude, Llama 3) ]
↓ (MCP 请求)
[ MCP Client (嵌入在 Agent 框架中) ]
↓ (JSON-RPC)
[ MCP Server (管理工具注册/鉴权) ]
↓
[ Tools: 数据库 / API / 文件系统 / 自定义函数 ]
💡 你在简历中提到的 “AI Agent 会话状态管理” ,就可以通过 MCP 集成:
- 一个
save_conversation工具(写入数据库)- 一个
search_knowledge_base工具(查向量库)
而无需硬编码到 Agent 逻辑中。
四、MCP vs 其他工具调用方案
| 方案 | 特点 | 局限 |
|---|---|---|
| MCP(Model Context Protocol) | 开源、标准化、跨模型、安全 | 新兴标准,生态尚在建设 |
| OpenAI Function Calling | 成熟、易用 | 仅限 OpenAI 生态 |
| LangChain Tools | 灵活、插件丰富 | 绑定 LangChain 框架 |
| 自定义 Tool Call | 完全可控 | 无法复用,维护成本高 |
🌐 MCP 的愿景:成为 AI 时代的 “USB 接口”——任何模型插上就能用任何工具。
Q12:用Java写一个算法,题目要求为接收一个字符串,由全0或0和1混合组成,需要判断是否所以的1在0之前,例如:110对,000对,101错(算法)()
A12:
public class Solution {
/**
* 判断字符串中是否所有 '1' 都在 '0' 之前(或全为 0 / 全为 1)
* @param s 输入字符串,仅包含 '0' 和 '1'
* @return true 表示合法,false 表示非法
*/
public static boolean allOnesBeforeZeros(String s) {
if (s == null || s.isEmpty()) {
return true; // 空串视为合法
}
boolean seenZero = false;
for (char c : s.toCharArray()) {
if (c == '0') {
seenZero = true;
} else if (c == '1') {
if (seenZero) {
// 已经见过 0,又出现 1 → 不合法
return false;
}
}
// 题目保证只有 0/1,可不处理其他字符
}
return true;
}
// 测试用例
public static void main(String[] args) {
System.out.println(allOnesBeforeZeros("110")); // true
System.out.println(allOnesBeforeZeros("000")); // true
System.out.println(allOnesBeforeZeros("111")); // true
System.out.println(allOnesBeforeZeros("10")); // true
System.out.println(allOnesBeforeZeros("101")); // false
System.out.println(allOnesBeforeZeros("010")); // false
System.out.println(allOnesBeforeZeros("0110")); // false
System.out.println(allOnesBeforeZeros("")); // true
}
}
个人评价:
本次面试除博客中提到的问题,还提问了实习经历及简历上的项目技术栈,但个人认为面试难度不高,知识点简单,没有深挖,但注重实操,如常用的Linux命令,算法题简单,总体面试顺利