All articles

Java 面试题总结

系统整理 Java 基础、面向对象、集合、并发、JVM、Spring 与数据库相关知识。

2025-10-26 · Updated 2025-11-24 · 30 分钟阅读

Java 基础与面向对象

Q1:哪个不属于面向对象特性?

题目: 以下哪个不属于面向对象的特性?

  • 复用 ❌(正确答案)
  • 封装 ✔
  • 多态 ✔
  • 继承 ✔

答案: 复用

知识点解析:

面向对象的四大特性:

  1. 封装(Encapsulation)
    • 将数据和方法封装在类中,隐藏内部实现细节
    • 通过访问修饰符(private、protected、public)控制访问权限
    • 提供公共接口供外部使用
public class BankAccount {
    private double balance;  // 封装:私有字段
    
    public void deposit(double amount) {  // 公共接口
        if (amount > 0) {
            balance += amount;
        }
    }
    
    public double getBalance() {
        return balance;
    }
}
  1. 继承(Inheritance)
    • 子类继承父类的属性和方法
    • 实现代码复用
    • 支持方法重写(Override)
class Animal {
    public void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {  // 继承
    @Override
    public void eat() {  // 方法重写
        System.out.println("Dog is eating");
    }
}
  1. 多态(Polymorphism)
    • 同一接口可以有不同的实现
    • 运行时多态(方法重写)
    • 编译时多态(方法重载)
// 运行时多态
Animal animal = new Dog();  // 父类引用指向子类对象
animal.eat();  // 调用子类重写的方法

// 编译时多态(方法重载)
public void print(int i) { }
public void print(String s) { }
  1. 抽象(Abstraction)
    • 抽象类和接口
    • 定义规范,隐藏实现细节
    • 通过抽象方法定义契约
// 抽象类
abstract class Shape {
    abstract double area();  // 抽象方法
}

// 接口
interface Drawable {
    void draw();  // 接口方法
}

扩展知识点:

  • 复用(Reusability):是面向对象的优势,但不是特性。通过继承、组合等方式实现代码复用
  • 组合 vs 继承:优先使用组合而非继承,提高灵活性
  • SOLID 原则:单一职责、开闭原则、里氏替换、接口隔离、依赖倒置

Java 并发与多线程

Q2:Thread 和 Runnable 正确说法?

题目: 关于 Thread 和 Runnable,以下哪个说法正确?

  • Thread.start() 才能开启新线程 ✔(正确答案)
  • Thread.run() 可以开启新线程 ❌
  • 实现 Runnable 接口必须重写 run() 方法 ✔
  • Thread 类实现了 Runnable 接口 ✔

答案: Thread.start() 才能开启新线程

知识点解析:

Thread 和 Runnable 的关系:

// Thread 类实现了 Runnable 接口
public class Thread implements Runnable {
    private Runnable target;
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

创建线程的方式:

  1. 继承 Thread 类

    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Thread running");
        }
    }
    
    MyThread thread = new MyThread();
    thread.start();  // 开启新线程
    
  2. 实现 Runnable 接口(推荐)

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Runnable running");
        }
    }
    
    Thread thread = new Thread(new MyRunnable());
    thread.start();  // 开启新线程
    
  3. 实现 Callable 接口(有返回值)

    class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Result";
        }
    }
    
    FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
    Thread thread = new Thread(futureTask);
    thread.start();
    String result = futureTask.get();  // 获取返回值
    

start() vs run():

  • start():启动新线程,JVM 调用 run() 方法
  • run():在当前线程中执行,不会创建新线程
Thread thread = new Thread(() -> System.out.println("Running"));

thread.start();  // 新线程执行,输出 "Running"
thread.run();    // 当前线程执行,输出 "Running"(但不是新线程)

Q3:哪个能让线程立刻停止?

题目: 以下哪个方法能让线程立刻停止?

  • stop()(已废弃,但确实立即终止)✔
  • interrupt()
  • suspend()(已废弃)✔
  • yield()

答案: stop()suspend()(都已废弃)

知识点解析:

已废弃的方法:

  1. **Thread.stop()**(已废弃)

    • 立即终止线程,可能导致数据不一致
    • 释放所有锁,可能导致对象状态损坏
  2. **Thread.suspend()**(已废弃)

    • 挂起线程,可能导致死锁
    • 如果线程持有锁,其他线程无法获取

正确的线程终止方式:

  1. 使用中断标志(推荐)

    class MyThread extends Thread {
        private volatile boolean running = true;
        
        @Override
        public void run() {
            while (running) {
                // 检查中断标志
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
                // 执行业务逻辑
            }
        }
        
        public void stopThread() {
            running = false;
        }
    }
    
  2. 使用 interrupt()

    Thread thread = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();  // 重新设置中断标志
                break;
            }
        }
    });
    
    thread.start();
    thread.interrupt();  // 中断线程
    

Q4:以下语句哪个会导致空指针?

题目: 以下哪个语句可能导致空指针异常?

  • (s != null) & (s.length() > 0) ✔(单 & 不短路)
  • (s != null) && (s.length() > 0) ❌(短路与)
  • (s == null) || (s.length() > 0) ❌(短路或)

答案: (s != null) & (s.length() > 0)

知识点解析:

逻辑运算符:

  1. 短路运算符(&&、||)
    • &&:左边为 false 时,右边不执行
    • ||:左边为 true 时,右边不执行
String s = null;

// 安全:短路与,s 为 null 时不执行 s.length()
if (s != null && s.length() > 0) {
    // 不会抛出异常
}

// 危险:非短路与,s 为 null 时仍执行 s.length()
if (s != null & s.length() > 0) {  // 抛出 NullPointerException
    // 会抛出异常
}
  1. 位运算符(&、|)
    • &:按位与,两边都执行
    • |:按位或,两边都执行
// 位运算符示例
int a = 5;  // 101
int b = 3;  // 011
int result = a & b;  // 001 = 1

Q5:ReentrantLock 默认是?

题目: ReentrantLock 默认是什么锁?

  • 公平锁 ❌
  • 非公平锁(non-fair)✔(正确答案)
  • 可重入锁 ✔
  • 不可重入锁 ❌

答案: 非公平锁

知识点解析:

ReentrantLock 的特点:

  1. 可重入锁(Reentrant)
    • 同一线程可以多次获取同一把锁
    • 避免死锁
ReentrantLock lock = new ReentrantLock();

public void method1() {
    lock.lock();
    try {
        method2();  // 可以再次获取锁
    } finally {
        lock.unlock();
    }
}

public void method2() {
    lock.lock();  // 同一线程可以再次获取
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}
  1. 公平锁 vs 非公平锁
// 非公平锁(默认)
ReentrantLock lock = new ReentrantLock();  // 非公平锁

// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);  // 公平锁

公平锁 vs 非公平锁对比:

特性公平锁非公平锁
获取顺序按照等待时间顺序抢占式
性能吞吐量低吞吐量高
适用场景需要公平性追求性能

公平锁实现原理:

  • 使用队列维护等待线程
  • 按照 FIFO 顺序获取锁

非公平锁实现原理:

  • 新线程可以直接尝试获取锁
  • 如果获取失败才进入队列

Q6:ReentrantLock.tryLock() 行为?

题目: ReentrantLock.tryLock() 的行为是什么?

  • tryLock() 不阻塞,立即返回 ✔(正确答案)
  • tryLock() 会阻塞等待 ❌
  • tryLock() 必须配合超时使用 ❌

答案: tryLock() 不阻塞,立即返回

知识点解析:

ReentrantLock 的获取锁方法:

  1. **lock()**:阻塞等待获取锁

    ReentrantLock lock = new ReentrantLock();
    lock.lock();  // 阻塞等待,直到获取锁
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
    
  2. **tryLock()**:尝试获取锁,不阻塞

    ReentrantLock lock = new ReentrantLock();
    if (lock.tryLock()) {  // 立即返回 true/false
        try {
            // 临界区
        } finally {
            lock.unlock();
        }
    } else {
        // 获取锁失败,执行其他逻辑
    }
    
  3. **tryLock(timeout, unit)**:带超时的尝试获取锁

    ReentrantLock lock = new ReentrantLock();
    try {
        if (lock.tryLock(5, TimeUnit.SECONDS)) {  // 最多等待 5 秒
            try {
                // 临界区
            } finally {
                lock.unlock();
            }
        } else {
            // 超时未获取到锁
        }
    } catch (InterruptedException e) {
        // 处理中断
    }
    

扩展知识点:

  • synchronized vs ReentrantLock

    • synchronized:JVM 层面,自动释放锁
    • ReentrantLock:API 层面,需要手动释放,功能更强大
  • Condition 条件变量

    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    
    // 等待条件
    lock.lock();
    try {
        while (!conditionMet) {
            condition.await();  // 释放锁并等待
        }
    } finally {
        lock.unlock();
    }
    
    // 通知条件
    lock.lock();
    try {
        conditionMet = true;
        condition.signal();  // 唤醒等待的线程
    } finally {
        lock.unlock();
    }
    

集合与容器类

Q7:哪个线程安全、且不允许 null?

题目: 以下哪个集合类线程安全且不允许 null 值?

  • HashMap ❌(线程不安全,允许 null)
  • ConcurrentHashMap ✔(线程安全,不允许 null)
  • Hashtable ✔(线程安全,不允许 null)
  • TreeMap ❌(线程不安全,不允许 null)

答案: ConcurrentHashMap 和 Hashtable

知识点解析:

HashMap vs ConcurrentHashMap vs Hashtable:

特性HashMapConcurrentHashMapHashtable
线程安全
允许 null key
允许 null value
锁粒度-分段锁/CAS表级锁
性能较高
迭代器Fail-FastFail-SafeFail-Fast

ConcurrentHashMap 实现原理:

  1. JDK 1.7:分段锁(Segment)

    // 将数据分成多个段,每个段独立加锁
    static final class Segment<K,V> extends ReentrantLock implements Serializable {
        // 每个段是一个小的 HashMap
    }
    
  2. JDK 1.8+:CAS + synchronized

    // 使用 CAS 和 synchronized 实现
    // 锁粒度更细,性能更好
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        // 使用 CAS 尝试插入
        // 如果失败,使用 synchronized 锁住链表头节点
    }
    

代码示例:

// HashMap(线程不安全)
Map<String, String> map = new HashMap<>();
map.put(null, "value");  // 允许 null key
map.put("key", null);    // 允许 null value

// ConcurrentHashMap(线程安全)
ConcurrentHashMap<String, String> concurrentMap = new ConcurrentHashMap<>();
// concurrentMap.put(null, "value");  // 抛出 NullPointerException
// concurrentMap.put("key", null);    // 抛出 NullPointerException

// 多线程安全使用
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        for (int j = 0; j < 1000; j++) {
            concurrentMap.put("key" + j, "value" + j);
        }
    }).start();
}

扩展知识点:

  • CopyOnWriteArrayList:写时复制,适合读多写少
  • **Collections.synchronizedMap()**:将普通 Map 包装成线程安全的
  • ConcurrentHashMap 的 size() 方法:是近似值,不是精确值

JVM 内存模型

Q9:方法局部变量存放在哪里?

题目: 方法中的局部变量存放在哪里?

  • 堆(Heap)❌
  • 方法区(Method Area)❌
  • 线程独享的栈(Stack)✔(正确答案)
  • 程序计数器(PC Register)❌

答案: 线程独享的栈

知识点解析:

JVM 内存区域:

  1. 线程共享区域:

    • 堆(Heap):对象实例、数组
    • 方法区(Method Area):类信息、常量、静态变量
    • 元空间(Metaspace):JDK 1.8+,替代永久代
  2. 线程私有区域:

    • JVM 栈(Stack):局部变量、方法参数、返回值
    • 本地方法栈(Native Method Stack):Native 方法
    • 程序计数器(PC Register):当前执行的字节码指令地址

栈内存结构:

栈帧(Stack Frame):
+-------------------+
| 局部变量表(Local Variables)|
+-------------------+
| 操作数栈(Operand Stack)   |
+-------------------+
| 动态链接(Dynamic Linking) |
+-------------------+
| 方法返回地址(Return Address)|
+-------------------+

代码示例:

public class MemoryExample {
    private static int staticVar = 1;  // 方法区(静态变量)
    private int instanceVar = 2;      // 堆(实例变量)
    
    public void method() {
        int localVar = 3;  // 栈(局部变量)
        String str = "hello";  // 栈(引用),堆(对象)
    }
}

内存分配示例:

public void example() {
    int a = 10;           // 栈:局部变量 a = 10
    String s = "hello";   // 栈:引用 s,堆:字符串对象 "hello"
    Object obj = new Object();  // 栈:引用 obj,堆:Object 对象
}

扩展知识点:

  • 栈溢出(StackOverflowError):递归调用过深
  • 堆溢出(OutOfMemoryError):对象过多,堆内存不足
  • 方法区溢出:类加载过多,常量池过大
  • 内存泄漏:对象引用未释放,导致无法 GC

Spring & Spring Boot

Q10:@Transactional 不能配置的是什么?

题目: @Transactional 注解不能配置以下哪个选项?

  • 事务传播行为(propagation)❌
  • 事务隔离级别(isolation)❌
  • 异常类注解数组回滚(rollbackFor)❌
  • 使用”异常类注解数组”回滚(错误方式)✔

答案: 使用”异常类注解数组”回滚(这是错误的配置方式)

知识点解析:

@Transactional 常用配置:

@Transactional(
    propagation = Propagation.REQUIRED,  // 传播行为
    isolation = Isolation.READ_COMMITTED,  // 隔离级别
    timeout = 30,  // 超时时间(秒)
    readOnly = false,  // 是否只读
    rollbackFor = Exception.class,  // 回滚的异常类型
    noRollbackFor = RuntimeException.class  // 不回滚的异常类型
)
public void transferMoney() {
    // 业务逻辑
}

事务传播行为(Propagation):

传播行为说明
REQUIRED(默认)如果存在事务则加入,否则创建新事务
REQUIRES_NEW总是创建新事务,挂起当前事务
SUPPORTS如果存在事务则加入,否则非事务执行
NOT_SUPPORTED非事务执行,挂起当前事务
MANDATORY必须在事务中,否则抛出异常
NEVER不能在事务中,否则抛出异常
NESTED嵌套事务

代码示例:

@Service
public class UserService {
    
    @Autowired
    private UserDao userDao;
    
    // 正确配置
    @Transactional(
        rollbackFor = Exception.class,  // 所有异常都回滚
        propagation = Propagation.REQUIRED
    )
    public void createUser(User user) {
        userDao.insert(user);
        if (user.getAge() < 0) {
            throw new IllegalArgumentException("年龄不能为负");
        }
    }
    
    // 错误配置示例(不存在这种写法)
    // @Transactional(rollbackFor = @Exception.class)  // 错误:不能使用注解数组
}

Q11:自动配置错误的描述?

题目: 关于 Spring Boot 自动配置,以下哪个描述是错误的?

  • 自动配置不会通过 @EnableAutoConfiguration 关闭 ❌(错误)✔
  • 自动配置基于类路径和条件注解
  • 可以通过 spring.autoconfigure.exclude 排除自动配置
  • 自动配置类通常以 AutoConfiguration 结尾

答案: 自动配置不会通过 @EnableAutoConfiguration 关闭(这是错误的描述)

知识点解析:

Spring Boot 自动配置原理:

  1. @EnableAutoConfiguration

    @SpringBootApplication  // 包含 @EnableAutoConfiguration
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
  2. 自动配置类示例

    @Configuration
    @ConditionalOnClass(DataSource.class)
    @EnableConfigurationProperties(DataSourceProperties.class)
    public class DataSourceAutoConfiguration {
        // 自动配置逻辑
    }
    
  3. 关闭自动配置的方式

    // 方式1:使用 @SpringBootApplication 的 exclude
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    public class Application {
    }
    
    // 方式2:使用配置文件
    // application.yml
    spring:
      autoconfigure:
        exclude:
          - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
    
    // 方式3:使用 @EnableAutoConfiguration 的 exclude
    @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
    public class Application {
    }
    

自动配置原理:

  • 基于类路径:检测类路径中是否存在特定类
  • 基于条件注解:@ConditionalOnClass、@ConditionalOnBean 等
  • 基于属性配置:@EnableConfigurationProperties

Q12:Bean 依赖顺序,A 在 B 后初始化?

题目: 如何让 BeanA 在 BeanB 之后初始化?

  • 在 BeanA 上写 @DependsOn("beanB") ✔(正确答案)
  • 在 BeanB 上写 @DependsOn("beanA")
  • 使用 @Order 注解 ❌
  • 使用 @Primary 注解 ❌

答案: 在 BeanA 上写 @DependsOn("beanB")

知识点解析:

@DependsOn 注解:

@Component
@DependsOn("beanB")  // BeanA 依赖于 BeanB,BeanB 先初始化
public class BeanA {
    @Autowired
    private BeanB beanB;
}

@Component
public class BeanB {
    public BeanB() {
        System.out.println("BeanB initialized");
    }
}

Bean 初始化顺序控制:

  1. @DependsOn:明确声明依赖关系

    @Component
    @DependsOn({"beanB", "beanC"})  // 依赖多个 Bean
    public class BeanA {
    }
    
  2. @Order:控制 Bean 的加载顺序(主要用于 List 注入)

    @Component
    @Order(1)  // 数字越小,优先级越高
    public class BeanA implements Ordered {
        @Override
        public int getOrder() {
            return 1;
        }
    }
    
  3. @Primary:指定主要 Bean(当有多个同类型 Bean 时)

    @Component
    @Primary  // 优先注入
    public class PrimaryBean implements Service {
    }
    
    @Component
    public class SecondaryBean implements Service {
    }
    

扩展知识点:

  • Bean 生命周期:实例化 → 属性注入 → 初始化 → 使用 → 销毁
  • @PostConstruct:初始化方法
  • @PreDestroy:销毁方法
  • InitializingBean 接口:实现 afterPropertiesSet() 方法

设计模式

Q13:用于动态给对象添加功能的是?

题目: 以下哪个设计模式用于动态给对象添加功能?

  • 装饰器模式(Decorator)✔(正确答案)
  • 代理模式(Proxy)❌
  • 适配器模式(Adapter)❌
  • 策略模式(Strategy)❌

答案: 装饰器模式

知识点解析:

装饰器模式(Decorator Pattern):

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有类的一个包装。

// 组件接口
interface Coffee {
    String getDescription();
    double getCost();
}

// 具体组件
class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Simple Coffee";
    }
    
    @Override
    public double getCost() {
        return 5.0;
    }
}

// 装饰器抽象类
abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
    
    @Override
    public String getDescription() {
        return coffee.getDescription();
    }
    
    @Override
    public double getCost() {
        return coffee.getCost();
    }
}

// 具体装饰器
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }
    
    @Override
    public double getCost() {
        return coffee.getCost() + 2.0;
    }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public String getDescription() {
        return coffee.getDescription() + ", Sugar";
    }
    
    @Override
    public double getCost() {
        return coffee.getCost() + 1.0;
    }
}

// 使用示例
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription());  // Simple Coffee, Milk, Sugar
System.out.println(coffee.getCost());  // 8.0

Java 中的装饰器模式应用:

  • java.io 包中的流类(InputStream、OutputStream 等)
  • java.util.Collections 中的装饰器方法

Q14:代理 vs 装饰

题目: 代理模式和装饰器模式的区别是什么?

答案对比:

区别代理模式装饰器模式
目的控制访问增强功能
关注点控制对象的访问动态添加功能
是否改变对象行为否(保持原行为)是(增强行为)
关系代理类和被代理类实现同一接口装饰器和被装饰类实现同一接口
使用场景远程代理、虚拟代理、保护代理动态添加功能,如 Java IO 流

代理模式示例:

// 接口
interface Image {
    void display();
}

// 真实对象
class RealImage implements Image {
    private String filename;
    
    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }
    
    private void loadFromDisk() {
        System.out.println("Loading " + filename);
    }
    
    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }
}

// 代理对象
class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;
    
    public ProxyImage(String filename) {
        this.filename = filename;
    }
    
    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);  // 延迟加载
        }
        realImage.display();
    }
}

装饰器模式示例:(见 Q13)

扩展知识点:

  • 适配器模式:改变接口,使不兼容的类可以一起工作
  • 桥接模式:将抽象与实现分离,使它们可以独立变化
  • 外观模式:为子系统提供统一的接口

Q15:状态模式关系?

题目: 状态模式中,上下文(Context)和状态(State)的关系是什么?

  • 上下文持有状态对象 ✔(正确答案)
  • 状态持有上下文对象 ❌
  • 上下文和状态相互独立 ❌
  • 状态继承上下文 ❌

答案: 上下文持有状态对象

知识点解析:

状态模式(State Pattern):

允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

// 状态接口
interface State {
    void handle(Context context);
}

// 具体状态
class ConcreteStateA implements State {
    @Override
    public void handle(Context context) {
        System.out.println("State A handling");
        context.setState(new ConcreteStateB());  // 切换到下一个状态
    }
}

class ConcreteStateB implements State {
    @Override
    public void handle(Context context) {
        System.out.println("State B handling");
        context.setState(new ConcreteStateA());  // 切换回上一个状态
    }
}

// 上下文类(持有状态对象)
class Context {
    private State state;  // 上下文持有状态对象
    
    public Context(State state) {
        this.state = state;
    }
    
    public void setState(State state) {
        this.state = state;
    }
    
    public void request() {
        state.handle(this);  // 委托给状态对象处理
    }
}

// 使用示例
Context context = new Context(new ConcreteStateA());
context.request();  // State A handling
context.request();  // State B handling

状态模式结构:

  • Context(上下文):维护一个 State 实例,定义当前状态
  • State(状态接口):定义状态的行为
  • ConcreteState(具体状态):实现特定状态的行为

扩展知识点:

  • 策略模式:策略可以互换,状态转换有规律
  • 状态机:有限状态机(FSM)的实现
  • 应用场景:订单状态、游戏角色状态、工作流等

数据库 & SQL

Q16:drop / truncate / delete 对比

题目: 关于 DROP、TRUNCATE、DELETE 的区别,以下哪个说法正确?

  • delete 可加 WHERE ✔(正确答案)
  • truncate 可加 WHERE ❌
  • drop 可加 WHERE ❌
  • 三者都可以回滚 ❌

答案: delete 可加 WHERE

知识点解析:

DROP、TRUNCATE、DELETE 对比:

特性DELETETRUNCATEDROP
类型DML(数据操作语言)DDL(数据定义语言)DDL
WHERE 子句支持不支持不支持
回滚可以(事务内)不可以(隐式提交)不可以
删除内容删除数据删除所有数据删除表结构
速度慢(逐行删除)快(直接删除数据页)最快
自增 ID不重置重置删除表
触发器触发不触发不触发
行锁表锁表锁

代码示例:

-- DELETE:删除指定数据
DELETE FROM users WHERE id = 1;  -- 可以加 WHERE
DELETE FROM users;  -- 删除所有数据(慢)

-- TRUNCATE:清空表数据
TRUNCATE TABLE users;  -- 不能加 WHERE,清空所有数据(快)

-- DROP:删除表结构
DROP TABLE users;  -- 删除整个表

使用场景:

  • DELETE:需要删除部分数据,需要回滚
  • TRUNCATE:快速清空表,不需要回滚
  • DROP:删除整个表结构

Q17:聚簇索引正确的是?

题目: 关于聚簇索引,以下哪个说法正确?

  • 可用排序树实现(即 B+Tree)✔(正确答案)
  • 只能有一个聚簇索引 ❌(错误:一个表只能有一个聚簇索引,但这是对的)
  • 聚簇索引就是主键索引 ❌(不一定)
  • 聚簇索引存储的是指针 ❌(存储的是数据本身)

答案: 可用排序树实现(即 B+Tree)

知识点解析:

聚簇索引(Clustered Index):

聚簇索引的索引和数据存储在一起,索引的叶子节点就是数据行。

特点:

  • 一个表只能有一个聚簇索引
  • 数据按照索引顺序物理存储
  • 通常使用 B+Tree 实现
  • 主键默认创建聚簇索引

B+Tree 结构:

            [根节点]
           /    |    \
      [内部节点] [内部节点] [内部节点]
      /  |  \   /  |  \   /  |  \
[叶子节点] [叶子节点] [叶子节点]
(存储数据)  (存储数据)  (存储数据)

聚簇索引 vs 非聚簇索引:

特性聚簇索引非聚簇索引
数量一个表只有一个可以有多个
存储索引和数据在一起索引和数据分离
叶子节点存储数据行存储指向数据行的指针
查找速度快(一次查找)较慢(两次查找)
实现B+TreeB+Tree

MySQL InnoDB 中的聚簇索引:

-- 主键自动创建聚簇索引
CREATE TABLE users (
    id INT PRIMARY KEY,  -- 自动创建聚簇索引
    name VARCHAR(50),
    email VARCHAR(100)
);

-- 如果没有主键,InnoDB 会:
-- 1. 选择第一个非空唯一索引作为聚簇索引
-- 2. 如果没有,创建一个隐藏的 row_id 作为聚簇索引

扩展知识点:

  • B+Tree vs B-Tree:B+Tree 非叶子节点只存储键值,叶子节点存储数据
  • 覆盖索引:查询的列都在索引中,不需要回表
  • 索引下推:在索引层面过滤数据,减少回表次数

Q18:SQL Join 哪些正确?

题目: 以下哪些 SQL 连接方式是正确的?

  • INNER JOIN 只返回两个表中匹配的行,如果在任何一个表中没有匹配的记录,那么这些行不会出现在结果集中。✔
  • LEFT JOIN 返回左表中所有行,即使在右表中没有匹配的行。对于右表中没有匹配的左表行,结果是NULL填充对应的行。✔
  • RIGHT JOIN 返回右表中所有行,即使在左表中没有匹配的行。对于左表中没有匹配的右表行,结果是NULL填充对应的行。✔
  • UNION ALL 操作符用于合并两个或更多SELECT语句的结果集,它会保留所有行,包括重复的行。如果两个查询结果集中有在所有列上都完全相同的行,UNION ALL 会全部保留,不会去重。
    (UNION:会自动去除重复的行(去重)
    UNION ALL:保留所有行,包括重复的行(不去重,性能更好))✔
  • 所以答案是 4 个都对

答案: 4 个都对

知识点解析:

SQL JOIN 类型:

  1. INNER JOIN(内连接)

    -- 只返回两个表中匹配的行
    SELECT u.name, o.order_id
    FROM users u
    INNER JOIN orders o ON u.id = o.user_id;
    
  2. LEFT JOIN(左连接)

    -- 返回左表所有行,右表匹配的行
    SELECT u.name, o.order_id
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id;
    -- 如果右表没有匹配,返回 NULL
    
  3. RIGHT JOIN(右连接)

    -- 返回右表所有行,左表匹配的行
    SELECT u.name, o.order_id
    FROM users u
    RIGHT JOIN orders o ON u.id = o.user_id;
    
  4. FULL OUTER JOIN(全外连接)

    -- MySQL 不支持,可以用 UNION 实现
    SELECT u.name, o.order_id
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    UNION
    SELECT u.name, o.order_id
    FROM users u
    RIGHT JOIN orders o ON u.id = o.user_id;
    
  5. UNION / UNION ALL

    -- UNION:去重
    SELECT name FROM users
    UNION
    SELECT name FROM customers;
    
    -- UNION ALL:不去重(性能更好)
    SELECT name FROM users
    UNION ALL
    SELECT name FROM customers;
    

JOIN 性能优化:

  • 确保 JOIN 条件有索引
  • 小表驱动大表
  • 避免在 JOIN 条件中使用函数

扩展知识点:

  • CROSS JOIN(笛卡尔积):返回两个表的所有组合
  • SELF JOIN(自连接):表与自身连接
  • 子查询 vs JOIN:JOIN 通常性能更好

HTTP & 网络

Q19:HTTP 状态码正确描述

题目: 关于 HTTP 状态码,以下哪个描述正确?

  • 3xx → 重定向,需要进一步操作 ✔(正确答案)
  • 2xx → 客户端错误 ❌
  • 4xx → 服务器错误 ❌
  • 5xx → 成功 ❌

答案: 3xx → 重定向,需要进一步操作

知识点解析:

HTTP 状态码分类:

1xx(信息性状态码):

  • 100 Continue:客户端应继续请求
  • 101 Switching Protocols:协议切换

2xx(成功状态码):

  • 200 OK:请求成功
  • 201 Created:资源创建成功
  • 204 No Content:请求成功,但无内容返回
  • 206 Partial Content:部分内容(用于断点续传)

3xx(重定向状态码):

  • 301 Moved Permanently:永久重定向
  • 302 Found:临时重定向
  • 304 Not Modified:资源未修改(使用缓存)

4xx(客户端错误状态码):

  • 400 Bad Request:请求错误
  • 401 Unauthorized:未授权
  • 403 Forbidden:禁止访问
  • 404 Not Found:资源不存在
  • 405 Method Not Allowed:方法不允许

5xx(服务器错误状态码):

  • 500 Internal Server Error:服务器内部错误
  • 502 Bad Gateway:网关错误
  • 503 Service Unavailable:服务不可用
  • 504 Gateway Timeout:网关超时

状态码速记:

  • 1xx:提示信息
  • 2xx:成功
  • 3xx:重定向,需要进一步操作
  • 4xx:客户端错误
  • 5xx:服务器错误

常见状态码应用场景:

// RESTful API 状态码使用
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    User created = userService.create(user);
    return ResponseEntity.status(HttpStatus.CREATED).body(created);  // 201
}

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    if (user == null) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).build();  // 404
    }
    return ResponseEntity.ok(user);  // 200
}

Q20:TCP/UDP 正确描述

题目: 关于 TCP 和 UDP,以下哪个描述正确?

  • TCP 与 UDP 都不是面向连接(错误:TCP 是面向连接的)❌
  • TCP 丢包后无法”继续传”(要靠重传)❌
  • 同端口可同时监听 TCP 和 UDP ✔(正确答案)
  • UDP 保证数据有序 ❌

答案: 同端口可同时监听 TCP 和 UDP

知识点解析:

TCP vs UDP 对比:

特性TCPUDP
连接面向连接(三次握手)无连接
可靠性可靠(确认、重传)不可靠
有序性保证有序不保证有序
速度较慢较快
头部开销20 字节8 字节
流量控制有(滑动窗口)
拥塞控制
应用场景HTTP、FTP、SMTPDNS、视频流、游戏

TCP 三次握手:

客户端                    服务器
  |                        |
  |----SYN (seq=x)-------->|
  |                        |
  |<--SYN-ACK(seq=y,ack=x+1)|
  |                        |
  |----ACK(ack=y+1)------->|
  |                        |

TCP 四次挥手:

客户端                    服务器
  |                        |
  |----FIN---------------->|
  |<----ACK---------------|
  |                        |
  |<----FIN---------------|
  |----ACK---------------->|
  |                        |

端口复用:

// TCP 和 UDP 可以使用相同的端口号
// 因为端口是在协议栈中区分的

// TCP 服务器
ServerSocket tcpSocket = new ServerSocket(8080);

// UDP 服务器(可以使用相同端口)
DatagramSocket udpSocket = new DatagramSocket(8080);

TCP 丢包处理:

  • TCP 通过确认机制重传机制保证可靠性
  • 发送方发送数据后等待确认(ACK)
  • 如果超时未收到确认,会重传数据
  • 接收方通过序列号保证数据有序

UDP 特点:

  • 无连接:不需要建立连接
  • 不可靠:不保证数据到达
  • 无状态:不维护连接状态
  • 适合实时应用:低延迟

扩展知识点:

  • HTTP/1.1 vs HTTP/2:HTTP/2 支持多路复用
  • HTTPS:HTTP + TLS/SSL 加密
  • WebSocket:全双工通信协议
  • QUIC:基于 UDP 的可靠传输协议

线程池 ThreadPoolExecutor

Q21:workQueue 是什么?

题目: ThreadPoolExecutor 中的 workQueue 是什么?

  • 阻塞队列 ✔(正确答案)
  • 普通队列 ❌
  • 同步队列 ❌
  • 优先级队列 ❌

答案: 阻塞队列

知识点解析:

ThreadPoolExecutor 核心参数:

ThreadPoolExecutor(
    int corePoolSize,              // 核心线程数
    int maximumPoolSize,           // 最大线程数
    long keepAliveTime,            // 线程存活时间
    TimeUnit unit,                 // 时间单位
    BlockingQueue<Runnable> workQueue,  // 工作队列(阻塞队列)
    ThreadFactory threadFactory,   // 线程工厂
    RejectedExecutionHandler handler  // 拒绝策略
)

线程池任务执行流程:

提交任务
  ↓
核心线程是否已满?
  ├─ 否 → 创建核心线程执行
  └─ 是 → 工作队列是否已满?
      ├─ 否 → 加入工作队列
      └─ 是 → 线程数是否达到最大值?
          ├─ 否 → 创建新线程执行
          └─ 是 → 执行拒绝策略

工作队列类型:

  1. ArrayBlockingQueue:有界数组队列

    new ThreadPoolExecutor(
        5, 10, 60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(100)  // 容量为 100
    );
    
  2. LinkedBlockingQueue:无界链表队列

    new ThreadPoolExecutor(
        5, 10, 60L, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>()  // 无界队列
    );
    
  3. SynchronousQueue:同步队列(不存储元素)

    new ThreadPoolExecutor(
        5, 10, 60L, TimeUnit.SECONDS,
        new SynchronousQueue<>()  // 直接提交,不存储
    );
    
  4. PriorityBlockingQueue:优先级队列

    new ThreadPoolExecutor(
        5, 10, 60L, TimeUnit.SECONDS,
        new PriorityBlockingQueue<>()  // 按优先级执行
    );
    

拒绝策略:

  1. AbortPolicy(默认):抛出异常
  2. CallerRunsPolicy:调用者执行
  3. DiscardPolicy:直接丢弃
  4. DiscardOldestPolicy:丢弃最老的任务

线程池使用示例:

// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5,                                    // 核心线程数
    10,                                   // 最大线程数
    60L,                                  // 线程存活时间
    TimeUnit.SECONDS,                     // 时间单位
    new LinkedBlockingQueue<>(100),      // 工作队列
    new ThreadFactory() {                 // 线程工厂
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("MyThread-" + t.getId());
            return t;
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
);

// 提交任务
executor.execute(() -> {
    System.out.println("Task executed");
});

// 关闭线程池
executor.shutdown();

扩展知识点:

  • Executors 工具类:提供常用线程池(但不推荐使用)
  • 线程池监控:监控线程池状态、任务数量等
  • 线程池调优:根据业务场景调整参数

综合算法题解

题:计算日程需要的最少行数(区间重叠)

题目描述: 给定一组时间区间,计算同时进行的最多会议数(即需要的最少会议室数量)。

方法: 小顶堆 + 排序

算法思路:

  1. 按开始时间排序所有区间
  2. 使用小顶堆存储当前正在进行的会议结束时间
  3. 遍历每个区间:
    • 如果堆顶的结束时间 <= 当前区间的开始时间,说明会议室已空,移除堆顶
    • 将当前区间的结束时间加入堆
    • 更新最大堆大小

代码实现:

import java.util.*;

class Interval {
    int start;
    int end;
    
    Interval(int start, int end) {
        this.start = start;
        this.end = end;
    }
}

public class MeetingRooms {
    public int minMeetingRooms(Interval[] intervals) {
        if (intervals == null || intervals.length == 0) {
            return 0;
        }
        
        // 按开始时间排序
        Arrays.sort(intervals, (a, b) -> a.start - b.start);
        
        // 小顶堆,存储结束时间
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        int ans = 0;
        
        for (Interval interval : intervals) {
            // 移除已结束的会议
            while (!pq.isEmpty() && pq.peek() <= interval.start) {
                pq.poll();
            }
            
            // 添加当前会议
            pq.offer(interval.end);
            
            // 更新最大会议室数量
            ans = Math.max(ans, pq.size());
        }
        
        return ans;
    }
}

时间复杂度: O(n log n)

  • 排序:O(n log n)
  • 遍历 + 堆操作:O(n log n)

空间复杂度: O(n)

  • 堆最多存储 n 个元素

扩展题目:

  1. 合并区间:合并重叠的区间
  2. 插入区间:在已排序的区间列表中插入新区间
  3. 无重叠区间:移除最少的区间使剩余区间不重叠

总结

核心知识点回顾

1. Java 基础与面向对象

  • 面向对象四大特性:封装、继承、多态、抽象
  • 复用是优势,不是特性
  • SOLID 原则

2. Java 并发与多线程

  • Thread 和 Runnable 的区别
  • 正确的线程终止方式(中断标志)
  • 逻辑运算符的短路特性
  • ReentrantLock:可重入、公平/非公平锁
  • tryLock() 不阻塞

3. 集合与容器类

  • ConcurrentHashMap:线程安全,不允许 null
  • HashMap vs ConcurrentHashMap vs Hashtable

4. JVM 内存模型

  • 局部变量存放在栈中
  • 线程共享:堆、方法区
  • 线程私有:栈、程序计数器、本地方法栈

5. Spring & Spring Boot

  • @Transactional 配置
  • 自动配置原理和关闭方式
  • @DependsOn 控制 Bean 初始化顺序

6. 设计模式

  • 装饰器模式:动态添加功能
  • 代理模式 vs 装饰器模式
  • 状态模式:上下文持有状态对象

7. 数据库 & SQL

  • DELETE vs TRUNCATE vs DROP
  • 聚簇索引:B+Tree 实现
  • SQL JOIN 类型

8. HTTP & 网络

  • HTTP 状态码分类
  • TCP vs UDP
  • 端口复用

9. 线程池

  • workQueue 是阻塞队列
  • 线程池执行流程
  • 拒绝策略

10. 算法

  • 区间重叠问题:小顶堆 + 排序

面试重点

  1. 并发编程:线程创建、同步机制、线程池
  2. 集合框架:HashMap、ConcurrentHashMap 原理
  3. JVM:内存模型、垃圾回收
  4. Spring:IoC、AOP、事务管理
  5. 设计模式:常用设计模式的应用
  6. 数据库:索引、事务、SQL 优化
  7. 网络:HTTP、TCP/IP 协议

学习建议

  1. 理论结合实践:不仅要理解概念,还要会写代码
  2. 深入源码:阅读 JDK、Spring 等框架源码
  3. 多做练习:算法题、项目实战
  4. 总结归纳:建立知识体系,形成思维导图
  5. 持续学习:关注新技术,保持学习热情

参考资料:

  • 《Java 并发编程实战》
  • 《深入理解 Java 虚拟机》
  • 《Spring 实战》
  • 《设计模式:可复用面向对象软件的基础》
  • 《高性能 MySQL》
  • 《HTTP 权威指南》

Originally published on mlangTse's Blog. View source