2025.06.07
1. Java 中操作字符串有哪些类型?它们之间有什么区别?
Java 主要有三种可操作“字符序列”的类型:
String
特点:不可变(immutable)、底层基于字符数组
char[]
适用:读多写少的场景,它一旦创建,内容就不可变,保证了线程安全
StringBuilder
特点:可变(mutable)、非线程安全,底层同样是
char[]
,扩容时按oldCapacity * 2 + 2
适用:单线程下大量拼接字符串的场景,效率比
String +
高,比StringBuffer
快
StringBuffer
特点:可变、线程安全(方法上用
synchronized
),同样基于char[]
适用:多线程需要频繁修改字符串的场景,但性能低于
StringBuilder
CharSequence
说明:
String
、StringBuilder
、StringBuffer
等都实现了它,表示一段可读的字符序列
2. String 类的常用方法及其作用
长度与内容
int length()
:返回字符串长度(字符个数)。boolean isEmpty()
:判断字符串是否为空(length() == 0
)。char charAt(int index)
:返回指定下标处的字符,0-based。char[] toCharArray()
:将字符串转换为char[]
。byte[] getBytes()
:按平台默认编码将字符串转换成字节数组。
比较与查找
boolean equals(Object)
:区分大小写比较内容是否相同。boolean equalsIgnoreCase(String)
:忽略大小写比较内容是否相同。int compareTo(String another)
:按字典序比较,返回负/零/正。int indexOf(String/char)
:查找某子串或字符第一次出现的位置,找不到返回-1
。int lastIndexOf(String/char)
:查找子串或字符最后一次出现的位置,找不到返回-1
。boolean contains(CharSequence)
:判断是否包含指定子串。boolean startsWith(String)
:判断是否以指定前缀开始。boolean endsWith(String)
:判断是否以指定后缀结束。
截取与分割
String substring(int beginIndex)
:截取从beginIndex
到末尾的子串。String substring(int beginIndex, int endIndex)
:截取从beginIndex
(含)到endIndex
(不含)的子串。String[] split(String regex)
:按正则表达式拆分,返回字符串数组。String trim()
:去除两端空白字符(space、tab、换行等),不影响中间空格。
替换
String replace(char oldChar, char newChar)
:全局替换字符。String replace(CharSequence target, CharSequence replacement)
:全局替换字符串(不支持正则)。String replaceAll(String regex, String replacement)
:按正则替换,所有匹配均被替换。String replaceFirst(String regex, String replacement)
:按正则替换,仅替换第一个匹配。
大小写 & 格式化
String toLowerCase()
:转为全小写。String toUpperCase()
:转为全大写。String format(String fmt, Object… args)
:按格式化字符串生成新字符串(类似printf
)。
连接与常量池
static String join(CharSequence delimiter, CharSequence… elements)
:将多个序列用delimiter
连接成一个字符串。String intern()
:将字符串加入常量池并返回池中引用;若已存在则返回已有引用,便于节省内存和快速比较。
其它
boolean matches(String regex)
:判断整个字符串是否匹配给定正则。拼接建议:大量拼接时推荐使用
StringBuilder
或StringBuffer
,最后调用toString()
得到新String
。
3. List、Set、Map 之间的区别是什么?
List
特点:有序、允许重复
常用实现:
ArrayList
、LinkedList
适用:需要按下标访问、允许重复元素的场景
Set
特点:无序(或排序)、不允许重复
常用实现:
HashSet
、LinkedHashSet
、TreeSet
适用:需要去重的场景
Map
特点:键值对结构,key 不可重复,value 可重复
常用实现:
HashMap
、LinkedHashMap
、TreeMap
适用:需要快速根据 key 查找 value 的场景
4. HashMap 的实现原理
底层结构:数组 + 链表/红黑树(当单个桶中元素过多时,链表转为红黑树以优化性能)
哈希定位:
hash(key)
→ 扰动后与(capacity - 1)
做位与,定位到数组下标插入:
若目标桶为空,直接插入
否则遍历链表/树,若 key 相同则覆盖 value,否则追加到末尾/树中
扩容:当元素数超过
loadFactor
(默认 0.75)时,容量翻倍,并重新rehash
所有元素查询:同哈希定位过程,遍历桶内链表/树,通过
equals
比较 key,找到则返回对应 value
5. ArrayList 和 LinkedList 的区别
ArrayList
底层结构:动态数组
Object[]
随机访问:O(1)
任意位置插入/删除:O(n)(需要移动元素)
尾部插入/删除:均摊 O(1)
适用:读多写少、需要频繁随机访问的场景
LinkedList
底层结构:双向链表
随机访问:O(n)
任意位置插入/删除:O(1)(只改动指针)
尾部操作:O(1)
适用:插入/删除操作频繁的场景
6. 创建线程池的几种方式
Executors 工厂方法
Executors.newFixedThreadPool(int n)
Executors.newCachedThreadPool()
Executors.newSingleThreadExecutor()
Executors.newScheduledThreadPool(int corePoolSize)
ThreadPoolExecutor 构造
new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), threadFactory, rejectedExecutionHandler );
ForkJoinPool
适合分治任务,内部使用工作窃取算法
Spring 注解方式
@EnableAsync
+ 自定义TaskExecutor
@Scheduled
定时任务线程池
7. 什么是死锁?怎么防止死锁?
死锁:两个或多个线程各自持有对方需要的资源并相互等待,导致都无法继续执行的状态
防止策略:
固定顺序加锁:所有线程按同一顺序请求锁,避免循环等待
尝试加锁(tryLock):使用带超时的
tryLock(timeout)
,超时释放已持有锁再重试锁分离/细化:缩短持锁时间,减少锁竞争
死锁检测与资源剥夺(复杂,少用)
8. Session 和 Cookie 的区别;Session 的工作原理
Cookie
存储位置:客户端(浏览器)
容量:≈4KB
生命周期:可设置过期时间
安全性:易被篡改
Session
存储位置:服务器
容量:受服务器内存/存储限制
生命周期:通常浏览器关闭或超时后失效
安全性:只在客户端保存
sessionId
,相对更安全
Session 工作原理
客户端首次请求时,服务器创建
HttpSession
并生成唯一sessionId
服务器通过
Set-Cookie: JSESSIONID=…
下发给客户端后续请求浏览器携带该 Cookie,服务器根据
sessionId
找到对应HttpSession
在
HttpSession
中存取用户信息、购物车等会话数据
9. 什么是 Spring Boot?Spring Boot 的优点
Spring Boot:基于 Spring 框架的快速开发平台,通过“约定大于配置”和自动化配置简化 Spring 应用搭建
优点:
开箱即用:内嵌 Tomcat/Jetty,无需外部容器
自动配置:根据类路径依赖自动装配常用组件
Starter POMs:一组 Starter 简化依赖管理
生产级监控:Actuator 提供健康检查、指标等
极简配置:
application.properties
/yml
一处配置即可
10. Spring Cloud 的核心组件有哪些?
Spring Cloud Config(配置中心)
Eureka / Consul / Zookeeper(服务注册与发现)
Ribbon(客户端负载均衡)
Feign(声明式 HTTP 客户端,集成 Ribbon)
Hystrix / Resilience4j(断路器)
Zuul / Spring Cloud Gateway(API 网关)
Bus(事件总线,用于广播配置变更)
Sleuth + Zipkin(分布式链路跟踪)
Stream(消息驱动微服务)
11. MyBatis 的一级缓存和二级缓存
一级缓存
作用域:
SqlSession
级别,同会话中相同语句与参数直接从缓存取默认开启;
commit
/rollback
/clearCache
会清空
二级缓存
作用域:Mapper(namespace)级别,跨
SqlSession
共享默认关闭,需要在全局与单个 mapper 中配置
<setting name="cacheEnabled" value="true"/>
与<cache/>
,且返回对象须可序列化
12. 自增表中删除后重启再插入,ID 会是多少?
InnoDB(默认):删除 ID 6、7 后剩 1~5,重启时自增值 =
MAX(id)+1 = 6
,插入的新记录 ID 为 6重启后:InnoDB 会根据表中现有的最大
id
(此例为 5)来重新计算下一个自增值,变成5 + 1 = 6
。未重启:自增计数器仍在内存里,继续累加,所以看到的是 8。
MyISAM:自增计数器保存在磁盘,重启不变,新记录 ID 为 8
13. ACID 是什么?
A (Atomicity,原子性):事务要么全部成功,要么全部失败(回滚)
C (Consistency,一致性):事务执行前后保持数据完整性约束
I (Isolation,隔离性):并发事务互不干扰,可设不同隔离级别
D (Durability,持久性):事务一旦提交,对数据的修改永久保存
14. MySQL 索引是怎么实现的?
B+ 树索引:最常用,非叶子节点存 key 和子节点指针;叶子节点存完整 key + 指向数据行的指针(聚簇索引直接存数据行)
Hash 索引(MEMORY 引擎):通过哈希表实现,等值查询快,范围查询和排序不支持
全文索引:基于倒排索引,支持
MATCH ... AGAINST
R-Tree 索引(MyISAM 空间索引):用于地理空间数据
15. JVM 运行时数据区
程序计数器 (PC Register):存放当前线程所执行字节码的行号指示器
Java 栈 (Stack):每个方法调用时创建的栈帧,存储局部变量、操作数栈、动态链接、方法出口等
本地方法栈 (Native Stack):为执行本地方法服务
堆 (Heap):存放所有对象实例和数组,是 GC 管理的重点区域
方法区 (Method Area):存放类信息、常量、静态变量、即时编译器编译后的代码等(HotSpot 中的 Metaspace)
运行时常量池 (Runtime Constant Pool):方法区的一部分,存放编译期产生的各种字面量和符号引用
直接内存 (Direct Memory):不属于 JVM 内存一部分,通过 NIO 等方式分配,用于高效 I/O
16.服务器被入侵了怎么办?
立即断网隔离
保存证据和日志
修改所有密码(包括数据库、应用等)
评估影响范围
备份核心数据
完全重置系统
重装后及时更新系统补丁
从可信备份恢复数据
加强安全配置和监控
编写事件报告
2025.06.11
Vue 2 的生命周期
Vue 2 中,每个组件实例在其生命过程中会经历四大阶段(创建 ▶ 挂载 ▶ 更新 ▶ 销毁),并在各阶段触发相应的生命周期钩子。
1. 创建阶段
beforeCreate
:实例刚被创建,data
、methods
等都尚未初始化,此时不能访问响应式数据或 DOM。created
:实例已完成数据观测、属性初始化,可在此时发起异步请求或初始化数据,但仍不可操作真实 DOM。
2. 挂载阶段
beforeMount
:在挂载开始前被调用,此时模板已编译但尚未插入到页面。mounted
:实例已挂载到页面,可在此访问和操作 DOM,例如获取ref
引用。
3. 更新阶段
beforeUpdate
:数据更新后、DOM 重新渲染前被调用,可在此检视即将更新的状态。updated
:组件 DOM 及数据同步完成后被调用,谨慎避免在此钩子中再修改数据以防无限循环。
4. 销毁阶段
beforeDestroy
:实例销毁前调用,可用于清理定时器、解绑全局事件等。destroyed
:实例已被销毁,所有指令和事件绑定已解除,需释放资源。
v-if
和 v-show
的区别
本质差异
v-if
:条件为假时,真实地从 DOM 中移除元素;再次切换为真时重新渲染元素,代价是销毁与重建。v-show
:始终渲染元素到 DOM,仅通过切换元素的 CSSdisplay
属性实现显示与隐藏。
适用场景
频繁切换:推荐使用
v-show
,因为切换只涉及样式变化,性能开销较小。很少切换:推荐使用
v-if
,避免初始渲染时加载过多不必要的节点。
Redis 的持久化方式
Redis 提供了如下两种核心持久化机制,可以单独或混合使用来平衡性能与可靠性:
1. RDB(Point‐in‐Time Snapshot)
在指定时间间隔触发数据快照,将整个内存数据集保存到磁盘文件(
.rdb
)。优点:对性能影响小,适合不要求极端数据安全的场景。
缺点:快照间隔内的写入若未保存则会丢失。
2. AOF(Append Only File)
将每次写命令追加记录到日志文件(
.aof
),服务启动时可通过重放日志恢复数据。优点:支持更多持久化策略(每秒刷盘、每操作刷盘等),可最大限度减少数据丢失。
缺点:AOF 文件体积较大,重写过程对 I/O 影响较明显。
3. 混合持久化(Hybrid)
在新版 Redis 中,可开启混合持久化模式,将快照与 AOF 结合,通过先写 RDB 形式的基础数据再追加 AOF 日志,提高重启效率和安全性。
什么情况下要建索引
建索引的最佳时机
设计表结构时:在已知业务场景下,预先为常用查询字段(如
WHERE
、JOIN
、ORDER BY
条件)创建索引,以避免后期大规模数据量导致性能瓶颈。性能调优阶段:监控查询日志,针对慢查询中频繁访问的列或组合列,添加必要的索引以提升检索速度。大量数据增长后:当表中数据量快速增长,原有索引已无法满足查询效率需求时,应评估并补充或重建索引。
索引的注意事项
避免过度索引:索引会增加写入和存储开销,应聚焦于最关键的查询场景。
选择合适类型:单列索引、复合索引、全文索引等需根据查询类型和数据特点选用。
定期维护:重建或优化碎片化严重的索引,以保证持续的查询性能。
InnoDB 存储引擎
默认页大小 16 KB,对应单表(file-per-table)最大表空间 64 TB;可通过调整
innodb_page_size
(32 KB/64 KB)扩展到 128 TB / 256 TB。单行大小受页大小与 65 535 字节行长限制约束,但不影响行数上限。
行数 ≈ 表空间大小 ÷ 平均行大小(例如 1 KB/行时,64 TB ≈ 64×10¹² 行)。
MyISAM 存储引擎
默认情况下,数据文件和索引文件各可增长到 256 TB,可通过
myisam_data_pointer_size
或MAX_ROWS
/AVG_ROW_LENGTH
扩展至 65 536 TB(256⁷−1 字节)。
行数并非硬限制
MySQL(无论 InnoDB 还是 MyISAM)都不对“行数”设固定上限,真正的极限由可用磁盘空间和文件系统决定。
海量数据场景下的性能保障
索引:对
WHERE
、JOIN
、ORDER BY
等高频字段建立单列或复合索引,可在数百万行级别保持毫秒级查询。分区/分表:当表行数达到 千万级以上,或需要对历史数据做归档清理时,可通过 RANGE/HASH 分区或水平分表,将数据拆分到多个逻辑单元,提升查询和维护效率。
1. 针对 flag
字段是否建单列索引
表规模:50 万 行
flag
分布:flag=1
返回 18 万(36%)flag=2
返回 12 万(24%)flag=3
返回 20 万(40%)
索引选择性 = 不同值数 ÷ 总行数 ≈ 3/500 000 ≈ 0.0006% → 极低
返回比例 均远超常见的 “10–20%” 索引使用阈值
结论:
单列索引在这种低基数、高返回比例场景下,MySQL 优化器通常不会走索引,仍然全表扫描
同时会增加写入和维护开销
不建议 单独为
flag
建索引
2. 索引匹配顺序与回表机制
2.1 单列索引 vs 复合索引(最左前缀原则)
单列索引:只在一列上建树,查询该列才能用上
复合索引:在多列(固定顺序)上建树
只有查询条件中包含最左侧那一列,才能利用该索引
例如索引
(a,b,c)
,只支持按a
、a,b
或a,b,c
过滤
2.2 InnoDB 的回表(Lookup)机制
聚簇索引(Clustered Index):主键树上存完整行
二级索引(Secondary Index):只存索引列 + 主键值
回表:
当二级索引命中后,若查询还需其他列,就必须“回表”——再根据主键到簇索引读整行
会多一次 I/O 开销
覆盖索引(Covering Index):
若一个索引(单列或复合)已包含了查询
SELECT
/WHERE
/ORDER BY
等所有列则可直接从该索引返回结果,无需回表
你知道什么情况下不走索引吗?
回答:
返回行数过多(选择性低)
当索引过滤后仍需读取的数据行超过表总行数的 ~20%,优化器估算“索引查找 + 回表”成本高于全表扫描,就会直接走全表扫描。
索引基数(Cardinality)或选择性(Selectivity)过低
对于取值极少(如只有 2–3 种状态)的字段,索引几乎退化为全表扫描,优化器不会使用。
查询条件中无可用索引列
未对 WHERE/JOIN/ORDER BY 中的列建索引,或索引列被函数包装,都会导致索引失效。
复合索引最左前缀不匹配
对
(a,b,c)
建的复合索引,若只按b
或(b,c)
查询,不满足最左前缀原则,索引不生效。
小表更优
当表极小(如几十行),全表扫描比索引查找更快,优化器也会选择全表扫描。
优化器剪枝(Prune)机制
默认
optimizer_prune_level=1
,优化器基于行数估算“剪掉”认为次优的执行计划,包括索引路径。
面试官提到 MySQL 在底层有一个以 “P” 开头的预估函数,它是怎样的?
回答:
这里指的正是优化器的**“Prune”**(剪枝)机制,由系统变量
optimizer_prune_level
控制:MySQL 优化器会先对各种访问路径(全表扫描、索引扫描)估算访问行数,然后跳过(prune)估算成本过高的路径,以加速查询计划生成。
默认
optimizer_prune_level=1
(启用剪枝);若觉得优化器漏掉了更优方案,可设置optimizer_prune_level=0
关闭剪枝。
如果在上述情况下优化器不走索引,如何强制让它走?
回答:
使用索引提示(Index Hint):
SELECT * FROM your_table FORCE INDEX (idx_flag) WHERE flag = 1;
复制编辑
SELECT * FROM your_table FORCE INDEX (idx_flag) WHERE flag = 1;
FORCE INDEX
会让优化器把全表扫描成本视为“无限高”,只要指定索引可用,就会使用它。
B+Tree 的物理存储结构
页(Page)单位:索引在磁盘上按 16 KB 为单位存储,每个页要么是内部节点(internal node),要么是叶子节点(leaf node)
内部节点:只存放分隔键和子页指针,用于快速定位到目标叶子页;最顶层有一个固定位置的根页,其地址保存在 InnoDB 数据字典中
叶子节点:
对于主键索引(聚簇索引),叶子页同时存放整行数据;
对于二级索引(非主键索引),叶子页存放索引列值和对应的主键值,然后再回表取数据。
链表结构:同一级别的页通过前后指针串成双向链表,便于范围扫描
首次查询 name='张三'
的访问流程
检查缓冲池(Buffer Pool):
查询发出时,会先在内存中的缓冲池查找包含对应索引页或数据页的缓存。
未命中时从磁盘加载:
如果对应页不在缓冲池,就触发一次或多次磁盘 I/O,将这些页读入缓冲池。
内存中完成 B+Tree 遍历:
加载到内存后,再做 B+Tree 根→中间→叶子节点的搜索,定位到
张三
的记录指针(主键)或完整行。
何时直接从内存读取?
缓冲池命中:若目标数据对应的页已在缓冲池中(之前被访问过或预热加载),查询会 直接在内存完成,无需任何磁盘 I/O。
大多数重复或热点访问:在高并发或周期性读取同一数据时,缓冲池命中率高,性能极佳。
“po”——InnoDB 的 Buffer Pool
Buffer Pool(缓冲池):InnoDB 在内存中为表和索引页分配的一块大区域,用于缓存磁盘页,提升读取速度。
配置建议:在专用数据库服务器上,通常将 50–80% 内存分配给缓冲池,以获得更高的命中率。
问题:MySQL 有哪几种存储引擎?
MySQL 提供了多种可选的存储引擎,以满足不同的场景需求:
InnoDB:默认引擎,支持事务(ACID)、行级锁、外键和崩溃恢复;适合 OLTP 高并发场景
MyISAM:传统引擎,查询性能较好但不支持事务与外键;适合只读或分析场景
MEMORY:所有数据存放在内存中,访问快速但重启后数据丢失;常用于临时表或缓存CSV:以 CSV 文本文件形式存储,便于与文本工具互通,但无索引支持
Archive:压缩存储大量历史数据,写入速度快但查询较慢;适合日志归档
MERGE:将多个 MyISAM 表合并为一个逻辑表,适用于分区模拟场景
FEDERATED:访问远程 MySQL 服务器上的表,提供分布式查询能力
NDB(Cluster):支持分布式集群,适合高可用高吞吐 OLTP 场景
问题:索引的“级别”有哪些?
MySQL 常见的索引类型(级别)包括:
主键索引(PRIMARY):
每张表只能有一个,不允许 NULL,用于唯一标识行;在 InnoDB 中即聚簇索引,叶子节点存放整行数据
唯一索引(UNIQUE):
保证索引列值唯一,可有多个,允许 NULL;提供唯一性约束
普通索引(INDEX):
最基础的非唯一索引,可加速等值或范围查询;可在多列上创建复合索引
全文索引(FULLTEXT):
基于倒排索引,用于大文本字段的自然语言检索;InnoDB 和 MyISAM 均支持,内置分词器处理关键词匹配
空间索引(SPATIAL):
用于地理空间数据类型的 R-Tree 索引,仅支持 MyISAM 和部分 InnoDB 版本;加速 GIS 查询
问题:在什么场景下,应该选择 InnoDB?
需要事务支持与崩溃恢复:InnoDB 支持 ACID 事务(COMMIT/ROLLBACK)和崩溃后自动恢复机制,能保证数据的一致性和可靠性,适用于对数据完整性要求高的场景。
高并发写入:采用行级锁(row-level locking),减少锁冲突,适合大量并发
INSERT
/UPDATE
/DELETE
操作的 OLTP 系统。外键约束:原生支持外键,可在数据库层面维护表间引用完整性,无需额外应用逻辑。
问题:在什么场景下,应该选择 MyISAM?
读密集型或只读分析:MyISAM 对读操作做了深度优化,若写入比例低于约 15%,使用 MyISAM 能获得更佳的查询性能。
无需事务与外键:不支持事务和外键约束,结构简单,适合内容管理系统、日志检索等对完整性要求不高的场景。
历史归档或快速统计:MyISAM 的表级锁在少量写入场景下影响可忽略,且其对于
COUNT(*)
等统计操作的速度优于 InnoDB,适合报表或归档库。
问题 1:MySQL(InnoDB)中的数据在磁盘上是如何存储的?
回答:
InnoDB 将表和索引存放在一个或多个表空间文件(
.ibd
)中,按B+Tree结构组织。页(Page):默认每页 16 KB(由
innodb_page_size
决定),分为内部节点(存放分隔键与子页指针)和叶子节点。聚簇索引(主键索引)的叶子节点里直接存储整行数据;二级索引的叶子节点只存索引列值+主键值,再通过主键“回表”获取其余列。
同一层的页通过前后指针串成双向链表,以加速范围扫描。
问题 2:什么时候会将数据加载到内存**(Buffer Pool)?
回答:
InnoDB 在内存中维护一个Buffer Pool,缓存表空间中的页。
首次访问到某页时,如果该页不在 Buffer Pool,就触发磁盘 I/O,将页读入内存。
若启用了读前置(read-ahead),InnoDB 会预读相邻页到 Buffer Pool,以提升顺序扫描效率。
新加载的页通常插入到 LRU 算法的中段,以区分“初次加载”与真正的“热点”页。
问题 3:什么时候会将内存中的数据写回到磁盘?
回答:
脏页刷新:Buffer Pool 中被修改但未落盘的页(脏页)由后台 Page Cleaner 线程按 LRU 策略刷写到磁盘,当脏页比例超过
innodb_max_dirty_pages_pct
时强制刷新。模糊检查点(Fuzzy Checkpoint):InnoDB 定期记录检查点,将一批脏页分批落盘,避免长时间阻塞并控制重启恢复量。
事务提交日志:重做日志(Redo Log)会在提交时写入磁盘(可配置
innodb_flush_log_at_trx_commit
决定同步策略),但对应的脏页可延后刷新。
问题 4:数据或空间何时被回收?
回答:
页面淘汰(Page Eviction)
Buffer Pool 使用分段 LRU:将页分为“新链”和“老链”,只有反复访问后才晋升到“新链”,淘汰时优先移除“老链”尾部的页,保护真正的热点。
Undo 日志清理(Purge)
InnoDB 的 MVCC 版本信息存放在 Undo 表空间,Purge 线程会清除不再被任何事务引用的历史版本,回收 Undo 空间。
问题 5:什么是“热点数据”,何时会被保留在内存?
回答:
热点页是指在缓冲池中被频繁访问的页。
在分段 LRU 中,只有在一段时间内(默认 1 秒)被至少两次访问的页,才从“老链”晋升到“新链”,被视为热点,后续不会轻易被淘汰。
充足的
innodb_buffer_pool_size
(通常占总内存的 50–80%)可确保大部分热点页长驻内存,提升命中率与性能。
1. Buffer Pool 是什么?
缓存结构:Buffer Pool 按 页(Page) 为单位管理,每页默认大小为 16 KB(可通过
innodb_page_size
调整)内存驻留:当 InnoDB 启动时,会在内存中分配
innodb_buffer_pool_size
大小的区域,用于缓存磁盘页分段管理:内部使用分段 LRU(Least Recently Used)算法,将页分为“新链”(new sublist)和“老链”(old sublist),初次加载的页先放入老链,再根据访问频率晋升为真正的热点
2. 读操作:何时从内存 vs 磁盘
缓冲池命中:执行查询时,InnoDB 首先在 Buffer Pool 查找所需页,如果存在则直接从内存读取,产生一次 Buffer Pool Read Request
未命中时加载:若页不在缓冲池,则触发磁盘 I/O,将页读入内存后再完成查询,此过程计为一次 Buffer Pool Read
读前置(Read-Ahead):InnoDB 可在后台预读相邻页到缓冲池,以优化顺序扫描场景,但仍需等待这些页加载完成才可命中
3. 写操作:脏页刷新与日志
脏页生成:对数据页的修改首先在 Buffer Pool 中进行,并将该页标记为“脏页”(Dirty Page),同时生成重做日志(Redo Log)条目
后台刷写:当脏页比例超过
innodb_max_dirty_pages_pct_lwm
(默认为 10%)时,Page Cleaner 线程会自动将脏页异步写回磁盘,以维护脏页水平和释放缓冲池空间检查点机制:InnoDB 周期性地执行模糊检查点,将一定范围的脏页批量落盘,并推进日志检查点(Checkpoint),以加快崩溃恢复速度
4. 缓存管理与淘汰
分段 LRU
新链 vs 老链:新加载的页先放在老链头部,只有在一段时间内被多次访问,才晋升到新链
淘汰策略:当缓冲池空间不足时,优先淘汰老链尾部的页,保护新链中的热点页免被挤出
Undo 日志清理:为支持多版本并发控制(MVCC),InnoDB 在 Undo 表空间中保存历史版本,Purge 线程会回收不再需要的 Undo 记录,避免占用过多内存和磁盘空间
5. 关键配置参数
innodb_buffer_pool_size
:决定 Buffer Pool 总大小,建议占主机内存的 60–90%innodb_buffer_pool_instances
:将 Buffer Pool 分割为多个实例,可减少高并发时的互斥锁争用,建议最多不要超过 16 个实例innodb_page_cleaners
:控制 Page Cleaner 线程数量,默认与innodb_buffer_pool_instances
相同,可根据 I/O 压力调整innodb_max_dirty_pages_pct_lwm
:脏页低水位阈值,控制早期刷新行为,避免脏页堆积影响性能
2025.06.14
问1:Spring Boot 的核心特性是什么?它的自动装配原理又是怎样的?
答:
自动配置(Auto-Configuration):
Spring Boot 会根据类路径下的依赖、已定义的 Bean 以及
application.properties
/YAML
等配置,自动配置常用组件,极大减少手动配置量启用入口由组合注解
@SpringBootApplication
(包含@EnableAutoConfiguration
)触发,扫描并加载候选的自动配置类列表
起步依赖(Starter Dependencies):
提供如
spring-boot-starter-web
、spring-boot-starter-data-jpa
等一键聚合常用库的 POM,简化依赖管理嵌入式服务器(Embedded Servers):
内置 Tomcat、Jetty、Undertow,打包即运行,无需额外部署外部容器
外部化配置(Externalized Configuration):
支持多种配置源:
application.properties
/YAML
、环境变量、命令行参数等,实现一套代码多环境部署
生产就绪功能(Actuator):
集成 Actuator 提供健康检查、指标监控、审计、日志级别动态调整等端点,便于运维管理
开发者工具(DevTools):
支持热重启、LiveReload、自动重启等特性,显著提升开发效率。
约定优于配置(Convention over Configuration):
默认合理,提供“开箱即用”体验,让开发者专注业务实现。
自动装配原理详解
候选自动配置类收集
Spring Factories 机制:启动时,
SpringFactoriesLoader
扫描所有 JAR 包下的META-INF/spring.factories
(Spring Boot 2.7+ 亦支持META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
)文件,加载EnableAutoConfiguration
接口对应的实现类列表
去重与导入
AutoConfigurationImportSelector#getCandidateConfigurations()
方法收集并去重后,通过ImportSelector
将合格的自动配置类导入应用上下文,生成 Bean 定义
条件注解过滤
候选自动配置类上大量使用
@Conditional…
注解:@ConditionalOnClass
/@ConditionalOnMissingClass
:基于类路径存在性决定。@ConditionalOnBean
/@ConditionalOnMissingBean
:基于容器中 Bean 的有无决定。@ConditionalOnProperty
:基于配置属性值决定。还包括
@ConditionalOnSingleCandidate
、@ConditionalOnResource
、@ConditionalOnJava
等多种维度的过滤
排除与扩展
排除自动配置:可在启动类使用
exclude
属性,或通过spring.autoconfigure.exclude
配置清单屏蔽不需要的自动配置自定义自动配置:编写自定义
spring-boot-autoconfigure
模块,定义自己的@Configuration
并在spring.factories
中声明,即可插入自动装配流程。
调试与诊断
启动时加
--debug
打印条件评估报告(Condition Evaluation Report),或通过 Actuator/conditions
端点查看哪些自动配置被加载/跳过,帮助快速定位问题。
问2:在 Spring Boot 中,如果启动时 不想 让它自动装配 Bean,该怎么办?
答:
在启动类上全局排除自动配置
@SpringBootApplication( exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class } ) public class Application { … }
该方式会在启动时直接忽略这两个自动配置类所提供的所有 Bean
在配置文件中排除
spring.autoconfigure.exclude=\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
与注解效果等同,适合无需改动代码即可灵活开启/关闭排除列表
测试环境中排除
@SpringBootTest( exclude = { DataSourceAutoConfiguration.class } ) public class RepositoryTests { … }
在单元测试或集成测试场景,通过
exclude
屏蔽不必要的自动配置,缩短上下文启动时间方法级可选依赖
设置
@Autowired(required = false)
,当对应 Bean 不存在时,Spring 不会报错:@Autowired(required = false) public void setFooService(FooService fooService) { … }
或使用
ObjectProvider<T>
/Optional<T>
,在缺少 Bean 时提供默认:@Bean public BarService barService(ObjectProvider<BarService> provider) { return provider.getIfAvailable(DefaultBarService::new); }
这样可以精细控制方法注入,在容器内无相关 Bean 时自动降级处理。
自定义自动配置扩展
若需完全替换或扩展 Spring Boot 默认行为,可在自定义的spring-boot-autoconfigure
模块中,于META-INF/spring.factories
声明新的配置类,并结合条件注解实现按需加载。
问3:在 Spring Boot 中,如果启动时 没有 对应的 Bean,但又想 加载这个 Bean,应该怎么办?
答:
使用
@ConditionalOnMissingBean
注解@Configuration public class MyServiceConfig { @Bean @ConditionalOnMissingBean(MyService.class) public MyService defaultMyService() { return new DefaultMyService(); } }
仅在容器中 不存在
MyService
类型的 Bean 时,才会执行并注册该defaultMyService()
,确保依赖该接口的组件始终有可用实现。注解属性详解
value
/type
:指定要检查的 Bean 类型(默认取方法返回值类型)。name
:按 Bean 名称匹配。annotation
:仅当容器中不存在带指定注解的 Bean 时生效。ignored
/ignoredType
:排除某些候选 Bean,不参与匹配。
默认搜索策略为SearchStrategy.ALL
,会在整个 BeanFactory 层级中查找。
与其他条件注解结合
你可以将其与@ConditionalOnProperty
、@ConditionalOnClass
、@ConditionalOnBean
等联合使用,实现更精细的加载控制。例如:@Bean @ConditionalOnMissingBean(MyRepo.class) @ConditionalOnProperty(name="app.repo.enabled", havingValue="true") public MyRepo defaultRepo() { … }
仅在容器缺失
MyRepo
且配置属性app.repo.enabled=true
时加载。调整加载顺序
在自动配置类上使用@AutoConfigureAfter(OtherConfig.class)
或@AutoConfigureBefore
,确保条件判断在或后于相关配置注册之后执行,避免由于加载时序导致的边界问题。实践建议
仅在自动配置模块中使用:将此注解限定在自动配置类,保持插件化、非侵入式特性。
覆盖与优先级:结合
@Primary
或开启spring.main.allow-bean-definition-overriding=true
控制 Bean 覆盖策略,确保自定义实现能够取代默认实现。
问4:CompletableFuture 的主要特性及常用方法
答:
核心特性
异步非阻塞(Asynchronous, Non-Blocking)
通过
supplyAsync()
和runAsync()
将任务提交到ForkJoinPool.commonPool()
或自定义Executor
,调用线程无需等待即可继续执行,从而实现非阻塞式并发。
链式组合(Composability)
实现
CompletionStage
接口,支持thenApply()
(同步映射)、thenCompose()
(扁平化异步)、thenAccept()
、thenRun()
等方法,可按需串联或分支异步任务流。
并行任务协调(Parallel Coordination)
静态方法
allOf()
可等待多个CompletableFuture
全部完成,anyOf()
可在任一完成时继续执行,便于多任务聚合或竞速场景。
异常处理(Error Handling)
提供
exceptionally()
(异常恢复)、handle()
(统一处理结果与异常)、whenComplete()
(副作用处理)等回调,能够优雅地捕获、补偿或记录异步执行过程中的错误。
手动完成与超时(Manual Completion & Timeouts)
方法
complete()
、completeExceptionally()
可手动触发正常或异常完成;Java 9+ 的orTimeout()
、completeOnTimeout()
支持超时后自动抛出或返回备用值。
线程安全(Thread Safety)
多线程对同一
CompletableFuture
调用complete()
、cancel()
等,只有首次生效,内部通过原子操作保证状态一致性。
常用方法及作用
1. 提交异步任务
CompletableFuture.runAsync(Runnable)
提交无返回值任务,返回CompletableFuture<Void>
。CompletableFuture.supplyAsync(Supplier<U>)
提交有返回值任务,返回CompletableFuture<U>
,适用于需要获取结果的异步操作。
2. 同步映射与扁平化
thenApply(Function<T,R>)
在前一阶段完成后,同步处理结果并返回新值,创建新的CompletableFuture<R>
。thenCompose(Function<T,CompletableFuture<U>>)
将前一阶段结果传给返回CompletableFuture<U>
的函数,避免嵌套CompletableFuture<CompletableFuture<U>>
。
3. 结果消费与后续执行
thenAccept(Consumer<T>)
在前一阶段完成后消费结果,不返回新值,常用于日志记录或触发事件。thenRun(Runnable)
在前一阶段完成后执行指定Runnable
,不依赖结果,适合触发独立后续逻辑。
4. 多任务合并
thenCombine(CompletableFuture<U>, BiFunction<T,U,R>)
等待两个CompletableFuture
同时完成,将它们的结果传入BiFunction
并返回合并后的新值。CompletableFuture.allOf(CompletableFuture<?>... )
等待所有给定的 futures 完成,返回CompletableFuture<Void>
,常与join()
或thenApply()
组合获取各个结果。CompletableFuture.anyOf(CompletableFuture<?>... )
返回最先完成的 future 的结果,适合竞速场景。
5. 异常与完成控制
exceptionally(Function<Throwable,T>)
捕获前一阶段异常并返回备用结果,恢复链式流程。handle(BiFunction<T,Throwable,R>)
同时接收正常结果或异常,统一生成新值。whenComplete(BiConsumer<T,Throwable>)
在执行完成(正常或异常)后回调,用于日志或清理,但不改变结果。complete(T)
/completeExceptionally(Throwable)
手动触发 future 成功或异常完成,用于外部控制。orTimeout(long,TimeUnit)
/completeOnTimeout(T,long,TimeUnit)
在指定时间后超时抛出异常或返回默认值,简化超时处理。