📚 Java基础面试题
1. 面向对象特性
- 封装:隐藏内部实现,提供公共访问方法
- 继承:子类继承父类的属性和方法
- 多态:同一操作作用于不同对象产生不同执行结果
- 抽象:隐藏复杂实现细节,只展示功能定义
2. String、StringBuffer、StringBuilder区别
java
String str = "hello"; // 不可变,每次修改创建新对象 StringBuffer sb = new StringBuffer(); // 线程安全,效率较低 StringBuilder sbd = new StringBuilder(); // 线程不安全,效率最高
3. == 和 equals() 区别
- ==:比较基本类型值,或引用类型的内存地址
- equals():默认比较地址,可重写为比较内容
4. Java集合框架
text
Collection ├── List (有序可重复) │ ├── ArrayList (数组结构) │ ├── LinkedList (双向链表) │ └── Vector (线程安全) ├── Set (无序唯一) │ ├── HashSet (哈希表) │ └── TreeSet (红黑树) └── Queue (队列) Map ├── HashMap (非线程安全) ├── Hashtable (线程安全) └── ConcurrentHashMap (分段锁)
🚀 JVM面试题
1. JVM内存区域
- 程序计数器:线程私有,记录当前线程执行位置
- 虚拟机栈:存储局部变量表、操作数栈等
- 本地方法栈:为Native方法服务
- 堆:所有线程共享,存储对象实例
- 方法区:存储类信息、常量、静态变量等
2. 垃圾回收算法
- 标记-清除:先标记后清除,产生碎片
- 复制算法:内存对半分,适合新生代
- 标记-整理:标记后整理,适合老年代
- 分代收集:新生代复制,老年代标记整理
3. 类加载机制
text
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
⚡ 并发编程面试题
1. 线程创建方式
java
// 方式1:继承Thread
class MyThread extends Thread {
public void run() {}
}
// 方式2:实现Runnable
class MyRunnable implements Runnable {
public void run() {}
}
// 方式3:实现Callable
class MyCallable implements Callable<String> {
public String call() {}
}
2. synchronized 和 Lock 区别
| 特性 | synchronized | Lock |
|---|---|---|
| 使用方式 | 关键字 | 接口 |
| 锁释放 | 自动 | 手动 |
| 中断响应 | 不支持 | 支持 |
| 公平性 | 非公平 | 可设置 |
3. volatile 关键字
- 保证可见性
- 禁止指令重排序
- 不保证原子性
🏗️ Spring框架面试题
1. IOC和AOP
- IOC:控制反转,将对象创建权交给容器
- DI:依赖注入,运行时动态注入依赖
- AOP:面向切面编程,实现横切关注点
2. Bean生命周期
text
实例化 → 属性赋值 → 初始化 → 使用 → 销毁
3. Spring Boot 自动配置
java
@SpringBootApplication 包含了: - @Configuration:标记配置类 - @EnableAutoConfiguration:启用自动配置 - @ComponentScan:组件扫描
🗃️ 数据库面试题
1. MySQL索引
- B+树索引:最常用,支持范围查询
- 哈希索引:精确查询快,不支持范围
- 全文索引:文本搜索
2. 事务特性(ACID)
- 原子性:要么全成功,要么全失败
- 一致性:事务前后数据完整
- 隔离性:事务间互不干扰
- 持久性:提交后永久保存
3. 隔离级别
| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | ✅ | ✅ | ✅ |
| 读已提交 | ❌ | ✅ | ✅ |
| 可重复读 | ❌ | ❌ | ✅ |
| 串行化 | ❌ | ❌ | ❌ |
🔧 常用框架面试题
1. MyBatis
xml
<!-- #{} 和 ${} 区别 -->
#{}: 预编译,防止SQL注入
${}: 直接拼接,有SQL注入风险
2. Redis
- 数据类型:String、Hash、List、Set、ZSet
- 持久化:RDB(快照)、AOF(日志)
- 缓存问题:穿透、击穿、雪崩
📝 高频场景题
1. 单例模式实现
java
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
2. HashMap实现原理
- 数组+链表+红黑树
- 扰动函数减少碰撞
- 扩容机制:2的幂次
💡 面试技巧
- STAR原则回答问题
- Situation:背景
- Task:任务
- Action:行动
- Result:结果
- 代码规范体现专业性
- 命名规范
- 异常处理
- 注释清晰
- 主动展示亮点
- 性能优化经验
- 问题解决案例
- 技术钻研深度
需要我详细展开某个部分吗?或者你有特定的面试目标(如初级、中级、高级)?
都展开说一下,我最近要面试需要复习
好的,我来详细展开每个部分,帮助你全面复习Java面试。
📚 一、Java基础(深度展开)
1. 面向对象三大特性详解
封装
java
public class User {
// 私有属性,隐藏内部状态
private String name;
private int age;
private List<String> permissions;
// 提供公共访问方法
public String getName() {
return name;
}
// 可以在setter中添加校验逻辑
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法");
}
this.age = age;
}
// 提供业务方法,不直接暴露内部集合
public void addPermission(String permission) {
if (permissions == null) {
permissions = new ArrayList<>();
}
// 可以添加权限校验逻辑
permissions.add(permission);
}
// 返回不可修改的集合,保护内部数据
public List<String> getPermissions() {
return Collections.unmodifiableList(permissions);
}
}
继承的细节
java
// 抽象类:可以有抽象方法和具体方法
public abstract class Animal {
protected String name;
// 抽象方法:子类必须实现
public abstract void makeSound();
// 具体方法:子类可以继承
public void eat() {
System.out.println(name + "正在吃东西");
}
// final方法:子类不能重写
public final void sleep() {
System.out.println("睡觉中...");
}
}
// 继承的关键点
public class Dog extends Animal {
// 必须实现抽象方法
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
// 可以重写父类方法
@Override
public void eat() {
super.eat(); // 调用父类方法
System.out.println("狗啃骨头");
}
// 不能重写final方法
// public void sleep() {} // 编译错误
}
多态的三种形式
java
public class PolymorphismDemo {
public static void main(String[] args) {
// 1. 编译时多态(方法重载)
Calculator calc = new Calculator();
calc.add(1, 2); // 两个整数
calc.add(1.0, 2.0); // 两个浮点数
calc.add(1, 2, 3); // 三个整数
// 2. 运行时多态(方法重写)
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.makeSound(); // 输出:汪汪汪
animal2.makeSound(); // 输出:喵喵喵
// 3. 参数多态(泛型)
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
}
}
class Calculator {
// 方法重载:参数个数不同
public int add(int a, int b) {
return a + b;
}
// 方法重载:参数类型不同
public double add(double a, double b) {
return a + b;
}
// 方法重载:参数个数不同
public int add(int a, int b, int c) {
return a + b + c;
}
}
2. String深度解析
java
public class StringDeepDive {
public static void main(String[] args) {
// 1. 字符串常量池
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = s3.intern(); // 手动入池
System.out.println(s1 == s2); // true(同一个常量池对象)
System.out.println(s1 == s3); // false(堆中新对象)
System.out.println(s1 == s4); // true(返回常量池对象)
// 2. 字符串拼接优化
String str1 = "a" + "b" + "c"; // 编译期优化为"abc"
String str2 = "a";
String str3 = str2 + "b"; // 运行时使用StringBuilder
// 3. 循环中拼接字符串的正确方式
// 错误方式:每次循环创建StringBuilder
String result = "";
for (int i = 0; i < 1000; i++) {
result += i; // 每次都会创建新对象
}
// 正确方式
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String correct = sb.toString();
// 4. String不可变性的好处
// - 线程安全:可以无锁共享
// - 缓存hash值:String的hashCode只计算一次
// - 字符串常量池:复用对象节省内存
}
}
3. 集合框架源码级理解
HashMap源码解析
java
public class HashMapSourceAnalysis {
// 1. 重要参数
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int TREEIFY_THRESHOLD = 8; // 链表转红黑树阈值
static final int UNTREEIFY_THRESHOLD = 6; // 红黑树转链表阈值
// 2. put方法流程
/*
* 1. 计算key的hash值:key.hashCode() ^ (key.hashCode() >>> 16)
* 2. 如果table为空,进行resize()初始化
* 3. 计算索引:i = (n - 1) & hash
* 4. 如果位置为空,直接插入
* 5. 如果不为空,判断key是否相等
* 6. 如果是树节点,调用红黑树插入
* 7. 如果是链表,遍历查找,如果找到则替换,否则在链表尾部插入
* 8. 插入后检查链表长度是否超过8,超过转红黑树
* 9. 检查size是否超过threshold,超过则resize()扩容
*/
// 3. get方法流程
/*
* 1. 计算hash值
* 2. 计算索引
* 3. 检查第一个节点,如果key相等则返回
* 4. 如果不相等,检查是树节点还是链表节点
* 5. 树节点调用树的查找方法
* 6. 链表节点遍历查找
*/
// 4. 扩容机制
/*
* 1. 新容量 = 旧容量 * 2
* 2. 创建新数组,容量为旧数组2倍
* 3. 重新计算索引:元素在新数组的位置要么是原位置,要么是原位置+旧容量
* 4. 因为hash & oldCap == 0的判断
*/
}
// ConcurrentHashMap源码特点
public class ConcurrentHashMapFeatures {
/*
* JDK7:分段锁机制
* - 内部使用Segment数组,每个Segment继承ReentrantLock
* - 默认16个Segment,支持16个线程并发写入
*
* JDK8:CAS + synchronized
* - 抛弃分段锁,使用CAS + synchronized实现
* - 锁粒度更细:只锁链表或红黑树的头节点
* - 并发度更高
*
* 重要方法:
* - put:先尝试CAS插入,失败则synchronized锁头节点
* - get:不加锁,使用volatile保证可见性
* - size:使用CounterCell数组降低竞争
*/
}
🚀 二、JVM深度解析
1. JVM内存区域详解
java
public class JVMMemoryAreas {
/*
* 1. 程序计数器(线程私有)
* - 记录当前线程执行的字节码指令地址
* - 唯一不会OOM的区域
*
* 2. Java虚拟机栈(线程私有)
* - 每个方法调用创建一个栈帧
* - 栈帧包含:局部变量表、操作数栈、动态链接、方法出口
* - StackOverflowError:栈深度超过限制
* - OutOfMemoryError:动态扩展时无法申请足够内存
*
* 3. 本地方法栈(线程私有)
* - 为Native方法服务
* - HotSpot将虚拟机栈和本地方法栈合二为一
*
* 4. Java堆(线程共享)
* - 存放对象实例
* - 垃圾回收的主要区域
* - 分代:新生代(Eden、Survivor0、Survivor1)、老年代
* - OutOfMemoryError:没有内存完成实例分配
*
* 5. 方法区(线程共享)
* - 存储类信息、常量、静态变量、即时编译后的代码
* - 永久代(JDK7) / 元空间(JDK8)
* - 运行时常量池:Class文件中的常量池
* - OutOfMemoryError:无法满足内存分配需求
*/
// 内存分配示例
public void memoryAllocationDemo() {
// 对象在Eden区分配
byte[] allocation1 = new byte[2 * 1024 * 1024]; // 2MB
// 大对象直接进入老年代
// -XX:PretenureSizeThreshold=3M
byte[] allocation2 = new byte[4 * 1024 * 1024]; // 4MB
}
}
2. 垃圾回收深入理解
java
public class GarbageCollectionDeepDive {
/*
* 1. 判断对象是否可回收
* - 引用计数法(无法解决循环引用)
* - 可达性分析(GC Roots)
* GC Roots包括:
* - 虚拟机栈引用的对象
* - 方法区静态属性引用的对象
* - 方法区常量引用的对象
* - 本地方法栈JNI引用的对象
*/
// 循环引用示例
public void circularReferenceDemo() {
class Node {
Node next;
}
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a;
a = null;
b = null;
// 即使循环引用,GC Roots不可达,仍会被回收
}
/*
* 2. 垃圾收集算法
*
* 标记-清除算法
* 优点:实现简单
* 缺点:产生内存碎片、效率不高
*
* 复制算法(新生代)
* 原理:将内存分为两块,只使用一块,回收时将存活对象复制到另一块
* 优点:不会产生碎片,实现简单
* 缺点:内存利用率只有50%
*
* 标记-整理算法(老年代)
* 原理:标记后,将所有存活对象向一端移动
* 优点:没有内存碎片
* 缺点:移动对象需要Stop The World
*
* 分代收集算法
* 新生代:复制算法(对象存活率低)
* 老年代:标记-清除或标记-整理(对象存活率高)
*/
/*
* 3. 垃圾收集器详解
*
* Serial收集器
* - 单线程,Client模式默认
* - 进行垃圾回收时,必须暂停所有工作线程
*
* ParNew收集器
* - Serial的多线程版本,Server模式首选
* - 只有它能与CMS配合工作
*
* Parallel Scavenge收集器
* - 目标是达到可控制的吞吐量
* - 自适应调节策略
*
* Serial Old收集器
* - Serial的老年代版本,标记-整理算法
*
* Parallel Old收集器
* - Parallel Scavenge的老年代版本
* - JDK1.6开始提供
*
* CMS收集器
* - 以获取最短回收停顿时间为目标
* - 流程:初始标记(STW) → 并发标记 → 重新标记(STW) → 并发清除
* - 缺点:CPU敏感、浮动垃圾、空间碎片
*
* G1收集器
* - 面向服务端,JDK9+默认
* - 将堆划分为多个Region
* - 可预测的停顿时间模型
*/
}
3. 类加载机制详解
java
public class ClassLoaderDeepDive {
/*
* 1. 类加载全过程
*
* 加载:
* - 通过类的全限定名获取二进制字节流
* - 将字节流静态存储结构转换为方法区运行时数据结构
* - 生成Class对象作为方法区这个类的访问入口
*
* 验证:
* - 文件格式验证(魔数、版本号等)
* - 元数据验证(是否有父类、是否继承final类等)
* - 字节码验证(数据流和控制流分析)
* - 符号引用验证(匹配性检查)
*
* 准备:
* - 为类变量分配内存并设置初始值(如static int a = 123;准备阶段a=0)
* - final static变量直接赋值为设定值
*
* 解析:
* - 将常量池内的符号引用替换为直接引用
* - 包括类或接口解析、字段解析、方法解析等
*
* 初始化:
* - 执行类构造器<clinit>()方法
* - 按顺序收集所有类变量的赋值动作和静态语句块
*/
// 类初始化时机
/*
* 1. 遇到new、getstatic、putstatic、invokestatic指令时
* 2. 使用java.lang.reflect进行反射调用时
* 3. 初始化子类时,父类未初始化先初始化父类
* 4. 虚拟机启动时,包含main方法的类
* 5. 使用JDK7动态语言支持时
*/
static class Parent {
static {
System.out.println("Parent init");
}
}
static class Child extends Parent {
static {
System.out.println("Child init");
}
}
public static void main(String[] args) {
// 不会触发初始化的情况
Parent[] parents = new Parent[10]; // 数组定义
System.out.println(Child.class); // 访问类常量
}
/*
* 2. 类加载器
*
* 启动类加载器(Bootstrap ClassLoader)
* - 加载 JAVA_HOME/lib 下的类
* - C++实现,Java中无法获取
*
* 扩展类加载器(Extension ClassLoader)
* - 加载 JAVA_HOME/lib/ext 下的类
* - Java实现,sun.misc.Launcher$ExtClassLoader
*
* 应用类加载器(Application ClassLoader)
* - 加载 classpath 下的类
* - 默认的线程上下文类加载器
*
* 自定义类加载器
* - 继承ClassLoader,重写findClass方法
*/
// 双亲委派模型
/*
* 工作流程:
* 1. 类加载器收到类加载请求
* 2. 先委派给父类加载器加载
* 3. 父类加载器无法加载时,才自己尝试加载
*
* 好处:
* - 避免类的重复加载
* - 保证Java核心API的安全
*/
}
⚡ 三、并发编程深度解析
1. 线程基础与线程池
java
public class ThreadDeepDive {
// 1. 线程状态转换
/*
* NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED
*
* NEW:新建未启动
* RUNNABLE:可运行状态(包含就绪和运行)
* BLOCKED:阻塞等待锁
* WAITING:无限期等待(wait、join、park)
* TIMED_WAITING:限期等待
* TERMINATED:终止
*/
// 2. 线程池参数详解
public void threadPoolParams() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize:核心线程数
10, // maximumPoolSize:最大线程数
60, // keepAliveTime:空闲线程存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), // 工作队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
/*
* 线程池工作原理:
* 1. 提交任务,如果当前线程数 < corePoolSize,创建新线程执行
* 2. 如果 >= corePoolSize,任务放入队列
* 3. 如果队列已满,且线程数 < maximumPoolSize,创建新线程
* 4. 如果线程数达到maximumPoolSize,执行拒绝策略
*
* 拒绝策略:
* AbortPolicy:抛出异常(默认)
* CallerRunsPolicy:调用者线程执行
* DiscardPolicy:丢弃任务
* DiscardOldestPolicy:丢弃队列最老的任务
*/
}
// 3. 四种线程池的隐患
/*
* FixedThreadPool
* - 核心线程数=最大线程数
* - 使用LinkedBlockingQueue(无界队列)
* - 隐患:队列可能无限增长,导致OOM
*
* CachedThreadPool
* - 核心线程0,最大线程Integer.MAX_VALUE
* - 使用SynchronousQueue
* - 隐患:线程数无限增长,导致OOM
*
* SingleThreadExecutor
* - 核心线程=最大线程=1
* - 使用LinkedBlockingQueue
* - 隐患:队列可能无限增长
*
* ScheduledThreadPool
* - 使用DelayedWorkQueue
* - 隐患:队列可能无限增长
*/
}
2. 锁的深入理解
java
public class LockDeepDive {
// 1. synchronized 原理
/*
* 底层实现:
* - 代码块:monitorenter + monitorexit
* - 方法:ACC_SYNCHRONIZED标志
*
* 锁升级过程(无锁 → 偏向锁 → 轻量级锁 → 重量级锁)
*
* 偏向锁:
* - 锁对象头记录线程ID
* - 同一线程再次获取锁,只需比较线程ID
*
* 轻量级锁:
* - CAS获取锁
* - 适用于线程交替执行
*
* 重量级锁:
* - 依赖操作系统的Mutex Lock
* - 线程阻塞和唤醒需要上下文切换
*/
// 2. ReentrantLock 原理
/*
* 基于AQS(AbstractQueuedSynchronizer)
*
* AQS原理:
* - 维护volatile int state(同步状态)
* - CLH队列(双向链表)
*
* 独占模式:
* tryAcquire → tryRelease
*
* 共享模式:
* tryAcquireShared → tryReleaseShared
*/
// 3. 死锁示例和排查
public void deadLockDemo() {
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock1) {
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lock2) {
System.out.println("t1完成");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
try { Thread.sleep(100); } catch (Exception e) {}
synchronized (lock1) {
System.out.println("t2完成");
}
}
});
t1.start();
t2.start();
// 排查方法:
// 1. jps 查看java进程
// 2. jstack <pid> 查看线程堆栈
// 3. jconsole/jvisualvm 图形化工具
}
// 4. volatile 和 happen-before
/*
* volatile特性:
* 1. 可见性:写操作立即刷新到主存,读操作从主存读取
* 2. 禁止指令重排序
*
* happen-before规则:
* 1. 程序次序规则
* 2. volatile变量规则
* 3. 锁规则
* 4. 传递性
* 5. 线程启动规则
* 6. 线程终止规则
* 7. 线程中断规则
* 8. 对象终结规则
*/
}
🏗️ 四、Spring框架源码级理解
1. IOC容器源码分析
java
public class SpringIOCDeepDive {
/*
* 1. BeanFactory 和 ApplicationContext 区别
* BeanFactory:延迟加载,第一次getBean才创建
* ApplicationContext:预加载,容器启动时创建
*
* 2. BeanFactory 继承体系
* BeanFactory
* └── HierarchicalBeanFactory
* └── ConfigurableBeanFactory
* └── AutowireCapableBeanFactory
* └── ListableBeanFactory
* └── ApplicationContext
*/
// 3. BeanDefinition 解析过程
/*
* XML配置方式:
* XmlBeanDefinitionReader
* 1. 加载XML文件(ResourceLoader)
* 2. 解析XML(DocumentLoader)
* 3. 注册BeanDefinition(BeanDefinitionRegistry)
*
* 注解配置方式:
* ClassPathBeanDefinitionScanner
* 1. 扫描指定包下的类
* 2. 过滤出带有@Component等注解的类
* 3. 生成BeanDefinition并注册
*/
// 4. Bean生命周期源码级别
@Component
public class BeanLifecycleDemo implements BeanNameAware, BeanFactoryAware,
InitializingBean, DisposableBean {
private String name;
public BeanLifecycleDemo() {
System.out.println("1. 实例化");
}
@Autowired
public void setName(String name) {
System.out.println("2. 属性赋值");
this.name = name;
}
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
System.out.println("4. BeanFactoryAware");
}
@PostConstruct
public void postConstruct() {
System.out.println("5. @PostConstruct");
}
@Override
public void afterPropertiesSet() {
System.out.println("6. InitializingBean");
}
@Bean(initMethod = "initMethod")
public void initMethod() {
System.out.println("7. init-method");
}
@PreDestroy
public void preDestroy() {
System.out.println("8. @PreDestroy");
}
@Override
public void destroy() {
System.out.println("9. DisposableBean");
}
// 完整流程:
// 实例化 → 属性赋值 → Aware接口 → BeanPostProcessor前置处理
// → @PostConstruct → InitializingBean → init-method
// → BeanPostProcessor后置处理 → 使用中 → @PreDestroy
// → DisposableBean → destroy-method
}
}
2. AOP原理深度解析
java
public class AOPDeepDive {
/*
* 1. AOP概念
* Aspect:切面(@Aspect)
* JoinPoint:连接点(方法调用)
* Advice:通知(@Before、@After等)
* Pointcut:切点(匹配连接点的表达式)
* Introduction:引入(为类添加新方法属性)
* Target Object:目标对象
* AOP Proxy:代理对象
* Weaving:织入
*/
// 2. 动态代理两种方式
/*
* JDK动态代理
* - 基于接口,实现InvocationHandler
* - 生成代理类实现目标接口
* - 适用于有接口的情况
*/
public class JdkProxyDemo {
public Object createProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("前置增强");
Object result = method.invoke(target, args);
System.out.println("后置增强");
return result;
}
);
}
}
/*
* CGLIB动态代理
* - 基于继承,实现MethodInterceptor
* - 生成目标类的子类
* - 适用于没有接口的类
*/
public class CglibProxyDemo implements MethodInterceptor {
public Object createProxy(Class<?> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("前置增强");
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置增强");
return result;
}
}
// 3. Spring AOP 选择策略
/*
* 如果目标类实现了接口,默认使用JDK动态代理
* 如果没有实现接口,使用CGLIB
* 可以通过配置强制使用CGLIB
* @EnableAspectJAutoProxy(proxyTargetClass = true)
*/
}
3. Spring事务管理
java
@Service
public class TransactionDeepDive {
/*
* 1. 事务传播行为
*
* REQUIRED(默认)
* - 支持当前事务,如果没有则创建新事务
*
* SUPPORTS
* - 支持当前事务,如果没有则以非事务方式执行
*
* MANDATORY
* - 支持当前事务,如果没有则抛出异常
*
* REQUIRES_NEW
* - 创建新事务,如果有当前事务则挂起
*
* NOT_SUPPORTED
* - 以非事务方式执行,如果有当前事务则挂起
*
* NEVER
* - 以非事务方式执行,如果有当前事务则抛出异常
*
* NESTED
* - 如果当前事务存在,则嵌套事务执行
*/
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 事务方法
}
// 2. 事务失效的场景
/*
* 1. 数据库引擎不支持事务(MyISAM)
* 2. 注解在非public方法上
* 3. 同一个类中方法调用(自调用)
* 4. 异常被catch捕获未抛出
* 5. 抛出检查异常(非RuntimeException)
* 6. 传播行为设置问题
*/
@Autowired
private TransactionDeepDive self;
public void callMethod() {
// 错误:事务失效
methodA(); // 自调用
// 正确:通过代理调用
self.methodA();
}
// 3. 事务隔离级别
/*
* DEFAULT:使用数据库默认
* READ_UNCOMMITTED:读未提交
* READ_COMMITTED:读已提交
* REPEATABLE_READ:可重复读
* SERIALIZABLE:串行化
*/
}
🗃️ 五、数据库深度解析
1. MySQL索引原理
sql
-- 1. B+树索引详解 /* * 为什么用B+树: * - 非叶子节点不存储数据,可以存储更多索引 * - 所有数据都在叶子节点,查询稳定 * - 叶子节点双向链表,范围查询高效 * * 聚簇索引(InnoDB) * - 数据和索引存储在一起 * - 主键索引就是聚簇索引 * - 叶子节点存储完整行数据 * * 非聚簇索引(MyISAM) * - 数据和索引分开存储 * - 叶子节点存储数据地址 */ -- 2. 索引使用原则 CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL, `age` int DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_name_age` (`name`,`age`), -- 复合索引 KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB; -- 最左前缀原则 EXPLAIN SELECT * FROM user WHERE name = '张三'; -- 使用索引 EXPLAIN SELECT * FROM user WHERE age = 20; -- 不使用索引 EXPLAIN SELECT * FROM user WHERE name = '张三' AND age = 20; -- 使用索引 -- 索引下推(ICP) SELECT * FROM user WHERE name LIKE '张%' AND age = 20; -- 5.6之前:根据name找到主键,回表查数据,再过滤age -- 5.6之后:根据name找到主键时,直接在引擎层过滤age -- 3. 索引失效场景 -- 对索引列使用函数 EXPLAIN SELECT * FROM user WHERE YEAR(create_time) = 2023; -- 隐式类型转换 EXPLAIN SELECT * FROM user WHERE name = 123; -- name是varchar类型 -- 不符合最左前缀 EXPLAIN SELECT * FROM user WHERE age = 20; -- like以%开头 EXPLAIN SELECT * FROM user WHERE name LIKE '%三'; -- or条件 EXPLAIN SELECT * FROM user WHERE name = '张三' OR age = 20;
2. SQL优化实战
sql
-- 1. 分页优化 -- 传统分页(越往后越慢) SELECT * FROM user ORDER BY id LIMIT 100000, 20; -- 优化方案1:记录上次位置 SELECT * FROM user WHERE id > 100000 ORDER BY id LIMIT 20; -- 优化方案2:子查询 SELECT * FROM user WHERE id >= (SELECT id FROM user ORDER BY id LIMIT 100000, 1) LIMIT 20; -- 2. 连接优化 -- 小表驱动大表 -- 假设user表100条,order表1万条 -- 推荐 SELECT * FROM user u INNER JOIN order o ON u.id = o.user_id; -- 3. 排序优化 -- 避免filesort CREATE INDEX idx_age_name ON user(age, name); -- 排序字段和查询字段一致 SELECT age, name FROM user ORDER BY age, name; -- 4. 分组优化 -- 使用索引 CREATE INDEX idx_user_date ON order(user_id, create_time); SELECT user_id, COUNT(*) FROM order WHERE create_time >= '2024-01-01' GROUP BY user_id;
3. MySQL锁机制
sql
-- 1. 行锁、表锁、间隙锁 -- 表锁 LOCK TABLES user READ; LOCK TABLES user WRITE; -- 行锁(InnoDB) -- 共享锁(S锁) SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE; -- 排他锁(X锁) SELECT * FROM user WHERE id = 1 FOR UPDATE; -- 间隙锁(解决幻读) -- 锁住的是索引记录之间的间隙 SELECT * FROM user WHERE age BETWEEN 10 AND 20 FOR UPDATE; -- 2. 死锁案例 -- 事务1 BEGIN; UPDATE user SET name = '张三' WHERE id = 1; UPDATE user SET name = '李四' WHERE id = 2; COMMIT; -- 事务2 BEGIN; UPDATE user SET name = '王五' WHERE id = 2; UPDATE user SET name = '赵六' WHERE id = 1; COMMIT; -- 3. 死锁排查 -- 查看当前事务 SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX; -- 查看锁等待 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; -- 查看锁等待事务 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS; -- 开启死锁日志 SET GLOBAL innodb_print_all_deadlocks = ON;
🔧 六、Redis深度解析
1. Redis数据结构底层实现
java
public class RedisDataStructure {
/*
* 1. String(SDS - Simple Dynamic String)
* - 预分配空间,减少内存重分配次数
* - 二进制安全
* - 兼容C字符串
*
* 2. List(quicklist)
* - 压缩列表(ziplist)+ 双向链表
* - 元素少时用ziplist节省内存
* - 元素多时转为双向链表
*
* 3. Hash
* - 元素少时用ziplist
* - 元素多时用dict(哈希表)
* - 渐进式rehash
*
* 4. Set
* - 元素都是整数且少时用intset
* - 否则用dict
*
* 5. ZSet(跳表)
* - 元素少时用ziplist
* - 否则用skiplist + dict
* - 跳表:多级索引实现O(logN)查询
*/
// 2. 跳表实现原理
/*
* 为什么用跳表不用平衡树?
* - 实现简单
* - 范围查询方便
* - 更容易控制索引层级
*
* 层级:随机生成,1级概率50%,2级25%,以此类推
* 查询:从最高层开始,找到最后一个小于目标值的节点,然后降层
*/
}
2. 缓存问题解决方案
java
@Service
public class RedisCacheSolution {
@Autowired
private StringRedisTemplate redisTemplate;
// 1. 缓存穿透(查询不存在的数据)
public String getDataWithPenetration(String key) {
// 从缓存获取
String value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
// 缓存不存在,从数据库查询
value = queryFromDB(key);
if (value != null) {
// 数据库存在,写入缓存
redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS);
} else {
// 数据库不存在,缓存空值(设置较短过期时间)
redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
}
return value;
}
// 2. 缓存击穿(热点key过期)
// 方案
