Java面试题大全(从基础到高级)

jxq
11
2026-05-01

Java面试题大全

从基础到高级,全面覆盖Java面试知识点

最后更新:2025-05-01


目录


第一部分:Java基础

1.1 数据类型

Q1: Java有哪些基本数据类型?各占多少字节?

类型 字节 位数 取值范围
byte 1 8 -128 ~ 127
short 2 16 -32768 ~ 32767
int 4 32 -2^31 ~ 2^31-1
long 8 64 -2^63 ~ 2^63-1
float 4 32 IEEE 754
double 8 64 IEEE 754
char 2 16 0 ~ 65535
boolean 1 8 true/false

包装类对应关系:

  • byte → Byte
  • short → Short
  • int → Integer
  • long → Long
  • float → Float
  • double → Double
  • char → Character
  • boolean → Boolean

Q2: int和Integer有什么区别?

// 基本类型
int a = 10;

// 包装类型
Integer b = new Integer(10);
Integer c = Integer.valueOf(10);  // 推荐方式,有缓存

// 自动装箱/拆箱
Integer d = 10;    // 自动装箱:Integer.valueOf(10)
int e = d;         // 自动拆箱:d.intValue()

// 缓存范围:-128 ~ 127
Integer f1 = 100;
Integer f2 = 100;
System.out.println(f1 == f2);  // true,使用缓存

Integer g1 = 200;
Integer g2 = 200;
System.out.println(g1 == g2);  // false,超出缓存范围

答案要点:

  • int是基本类型,Integer是包装类
  • Integer提供了更多方法(parseInt、toString等)
  • Integer有缓存机制(-128~127)
  • 泛型只能使用Integer

Q3: String能被继承吗?为什么?

public final class String implements java.io.Serializable, 
    Comparable<String>, CharSequence {
    // ...
}

答案:不能,String被final修饰。

原因:

  1. 安全性:String常用于敏感信息(URL、文件路径、密码),不可继承避免被恶意篡改
  2. 效率:不可变性支持字符串常量池,节省内存
  3. 线程安全:不可变对象天生线程安全
  4. 哈希缓存:hashCode可以缓存,提高Map性能

1.2 字符串

Q4: String、StringBuilder、StringBuffer的区别?

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全 安全(不可变) 不安全 安全(synchronized)
性能 低(频繁创建) 高 中
适用场景 少量字符串操作 单线程大量操作 多线程大量操作
// String:每次修改都创建新对象
String s1 = "Hello";
s1 = s1 + " World";  // 创建了新对象

// StringBuilder:原地修改
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");  // 同一对象

// StringBuffer:线程安全版本
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World");

Q5: String s = new String(“abc”)创建了几个对象?

答案:1个或2个

  1. 如果字符串常量池中不存在"abc":

    • 常量池中创建"abc"(1个)
    • 堆中创建String对象(1个)
    • 共2个对象
  2. 如果字符串常量池中已存在"abc":

    • 只在堆中创建String对象(1个)
String s1 = "abc";           // 常量池
String s2 = new String("abc"); // 堆 + 常量池检查
System.out.println(s1 == s2);  // false
System.out.println(s1 == s2.intern()); // true,intern()返回常量池引用

Q6: String常用的方法有哪些?

// 获取
length()              // 长度
charAt(int index)     // 指定位置字符
substring(int begin, int end)  // 截取

// 判断
isEmpty()             // 是否为空
equals(Object obj)    // 内容比较
equalsIgnoreCase()    // 忽略大小写比较
contains(CharSequence) // 是否包含
startsWith(String)    // 是否以指定前缀开始
endsWith(String)      // 是否以指定后缀结束

// 转换
toCharArray()         // 转字符数组
toLowerCase()         // 转小写
toUpperCase()         // 转大写
trim()                // 去首尾空格

// 查找
indexOf(int ch)       // 字符首次出现位置
lastIndexOf(int ch)   // 字符最后出现位置

// 替换
replace(char old, char new)  // 替换字符
replaceAll(String regex, String replacement)  // 正则替换

// 分割
split(String regex)   // 按正则分割

// 拼接
concat(String str)    // 拼接字符串
String.join(",", list) // 静态方法拼接

1.3 运算符

Q7: == 和 equals 的区别?

// == 比较规则
// 1. 基本类型:比较值
int a = 10, b = 10;
System.out.println(a == b);  // true

// 2. 引用类型:比较内存地址
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);  // false

// equals:默认比较地址,可重写比较内容
System.out.println(s1.equals(s2));  // true(String重写了equals)

// Object类默认equals
public boolean equals(Object obj) {
    return (this == obj);  // 默认就是==
}

// String类重写equals
public boolean equals(Object anObject) {
    if (this == anObject) return true;
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // 逐字符比较
        // ...
    }
    return false;
}

答案要点:

类型 == equals
基本类型 比较值 不适用
引用类型 比较地址 默认比较地址,可重写比较内容

Q8: hashCode()和equals()的关系?

规则:

  1. equals返回true,hashCode必须相同
  2. hashCode相同,equals不一定返回true(哈希冲突)
  3. 重写equals必须重写hashCode
// 为什么必须同时重写?
public class Person {
    private String name;
    private int age;
    
    // 只重写equals
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj instanceof Person) {
            Person p = (Person) obj;
            return name.equals(p.name) && age == p.age;
        }
        return false;
    }
    
    // 必须重写hashCode
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// HashSet场景
Set<Person> set = new HashSet<>();
Person p1 = new Person("Tom", 20);
Person p2 = new Person("Tom", 20);
set.add(p1);
set.add(p2);
// 如果不重写hashCode,p1和p2会有不同的hashCode
// 导致HashSet认为它们是不同的对象,都添加进去

1.4 流程控制

Q9: switch能作用于哪些数据类型?

// 支持的类型
byte, short, int, char      // 基本类型
Byte, Short, Integer, Character  // 包装类
String                      // JDK 7+
enum                        // 枚举

// 不支持
long, float, double, boolean

// 示例
String color = "red";
switch (color) {
    case "red":
        System.out.println("红色");
        break;
    case "blue":
        System.out.println("蓝色");
        break;
    default:
        System.out.println("未知");
}

Q10: break、continue、return的区别?

for (int i = 0; i < 10; i++) {
    if (i == 3) break;      // 跳出整个循环
    if (i == 5) continue;   // 跳过本次循环,继续下一次
    if (i == 7) return;     // 结束整个方法
    System.out.println(i);
}

// 带标签的break
outer:
for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if (i + j == 15) break outer;  // 跳出外层循环
    }
}

1.5 数组

Q11: 数组和ArrayList的区别?

特性 数组 ArrayList
长度 固定 动态扩容
类型 基本类型+引用类型 只能引用类型
性能 高 略低(封装开销)
功能 基础 丰富(增删改查)
// 数组
int[] arr = new int[10];
arr[0] = 1;
int len = arr.length;  // 属性

// ArrayList
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
int len = list.size();  // 方法

Q12: 数组有哪些常见操作?

// 排序
int[] arr = {3, 1, 4, 1, 5};
Arrays.sort(arr);  // [1, 1, 3, 4, 5]

// 二分查找(必须先排序)
int index = Arrays.binarySearch(arr, 3);

// 转字符串
String s = Arrays.toString(arr);  // "[1, 1, 3, 4, 5]"

// 填充
Arrays.fill(arr, 0);  // [0, 0, 0, 0, 0]

// 复制
int[] copy = Arrays.copyOf(arr, 10);

// 比较
boolean equals = Arrays.equals(arr1, arr2);

1.6 关键字

Q13: final、finally、finalize的区别?

关键字 作用
final 修饰类、方法、变量
finally 异常处理中的代码块
finalize Object类的方法(已废弃)
// final
final class A {}           // 类不能被继承
final void method() {}     // 方法不能被重写
final int x = 10;          // 变量不能被修改(常量)

// finally
try {
    // 可能异常的代码
} catch (Exception e) {
    // 异常处理
} finally {
    // 无论是否异常都会执行(通常用于资源释放)
}

// finalize(已废弃,JDK 9+)
protected void finalize() throws Throwable {
    super.finalize();
    // 对象被回收前调用,不推荐使用
}

Q14: static关键字的作用?

public class Demo {
    // 静态变量(类变量)
    static int count = 0;
    
    // 静态代码块(类加载时执行,只执行一次)
    static {
        System.out.println("类加载");
    }
    
    // 静态方法
    public static void staticMethod() {
        // 只能访问静态成员
        // 不能使用this、super
    }
    
    // 静态内部类
    static class InnerClass {}
}

// 调用方式
Demo.count = 10;
Demo.staticMethod();

static特点:

  1. 属于类,不属于对象
  2. 随类加载而加载
  3. 优先于对象存在
  4. 可以通过类名直接访问

Q15: this和super的区别?

关键字 作用
this 当前对象引用
super 父类对象引用
public class Child extends Parent {
    private String name;
    
    public Child(String name) {
        super("parent");  // 调用父类构造器(必须第一行)
        this.name = name; // 当前对象属性
    }
    
    @Override
    public void method() {
        super.method();   // 调用父类方法
        this.method();    // 调用当前对象方法
    }
}

第二部分:面向对象

2.1 三大特性

Q16: 面向对象的三大特性是什么?

特性 说明
封装 隐藏实现细节,提供公共访问方式
继承 子类继承父类的属性和方法
多态 同一行为的不同实现形式
// 封装
public class Person {
    private String name;  // 私有属性
    
    public String getName() {  // 公共访问方式
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

// 继承
public class Student extends Person {
    private String school;
    
    // 继承了Person的属性和方法
}

// 多态
Person p = new Student();  // 父类引用指向子类对象
p.getName();  // 调用子类的方法(如果重写了)

Q17: 访问修饰符有哪些?

修饰符 本类 同包 子类 其他
public ✓ ✓ ✓ ✓
protected ✓ ✓ ✓ ✗
default ✓ ✓ ✗ ✗
private ✓ ✗ ✗ ✗

2.2 接口与抽象类

Q18: 接口和抽象类的区别?

特性 接口 抽象类
关键字 interface abstract class
继承 多实现 单继承
成员变量 只能public static final 任意
构造方法 无 有
方法 JDK8前全抽象,之后可有默认方法 可抽象可具体
设计理念 "像…"的能力 "是…"的关系
// 接口
interface Flyable {
    int MAX_HEIGHT = 10000;  // 默认public static final
    
    void fly();  // 默认public abstract
    
    // JDK 8: 默认方法
    default void defaultMethod() {
        System.out.println("默认方法");
    }
    
    // JDK 8: 静态方法
    static void staticMethod() {
        System.out.println("静态方法");
    }
    
    // JDK 9: 私有方法
    private void privateMethod() {
        System.out.println("私有方法");
    }
}

// 抽象类
abstract class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    public abstract void eat();  // 抽象方法
    
    public void sleep() {        // 具体方法
        System.out.println("睡觉");
    }
}

Q19: 什么时候用接口,什么时候用抽象类?

使用接口:

  • 需要多继承
  • 定义行为规范,不关心实现
  • 不同类有共同行为但无继承关系

使用抽象类:

  • 需要共享代码
  • 需要成员变量
  • 需要非public成员
  • 需要构造方法
// 接口:行为能力
interface Flyable { void fly(); }
interface Swimable { void swim(); }

// 抽象类:共同特征
abstract class Animal {
    protected String name;
    public abstract void eat();
}

// 组合使用
class Duck extends Animal implements Flyable, Swimable {
    @Override
    public void eat() { }
    @Override
    public void fly() { }
    @Override
    public void swim() { }
}

2.3 重载与重写

Q20: 重载和重写的区别?

特性 重载(Overload) 重写(Override)
位置 同一类中 子父类之间
方法签名 方法名相同,参数列表不同 完全相同
返回类型 无关 相同或协变
访问权限 无关 不能更严格
异常 无关 不能更宽泛
// 重载:同一类中,参数列表不同
public 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; }
}

// 重写:子类重写父类方法
class Parent {
    public void show() { System.out.println("Parent"); }
}

class Child extends Parent {
    @Override
    public void show() { System.out.println("Child"); }
}

Q21: 重写的规则有哪些?

class Parent {
    protected Number method() throws IOException { return 1; }
}

class Child extends Parent {
    // ✓ 正确重写
    @Override
    public Integer method() throws FileNotFoundException { 
        return 2; 
    }
    
    // ✗ 编译错误
    // private Number method()  // 访问权限更严格
    // public Object method()   // 返回类型不兼容
    // public Number method() throws Exception  // 异常更宽泛
}

重写规则:

  1. 方法名、参数列表必须相同
  2. 返回类型相同或为子类(协变返回类型)
  3. 访问权限不能更严格
  4. 抛出异常不能更宽泛
  5. final方法不能重写
  6. static方法不能重写(可隐藏)

2.4 构造方法

Q22: 构造方法的特点?

public class Person {
    private String name;
    private int age;
    
    // 无参构造
    public Person() {
        this("Unknown", 0);  // 调用其他构造器(必须第一行)
    }
    
    // 有参构造
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 私有构造(单例模式)
    private Person(String name) {
        this.name = name;
    }
}

特点:

  1. 名字与类名相同
  2. 没有返回类型(不是void)
  3. 可以重载
  4. 不能被继承
  5. 默认提供无参构造(如果没写任何构造器)

Q23: 子类构造方法如何调用父类构造方法?

class Parent {
    private String name;
    
    public Parent(String name) {
        this.name = name;
    }
}

class Child extends Parent {
    private int age;
    
    public Child(String name, int age) {
        super(name);  // 必须第一行,调用父类构造
        this.age = age;
    }
    
    public Child(int age) {
        super("default");  // 显式调用
        this.age = age;
    }
}

规则:

  • 子类构造器必须调用父类构造器(显式或隐式)
  • super()必须在第一行
  • 不写super()时,默认调用父类无参构造
  • 父类没有无参构造时,必须显式调用

2.5 内部类

Q24: 内部类的分类?

public class Outer {
    private int x = 10;
    
    // 成员内部类
    class Inner {
        public void method() {
            System.out.println(x);  // 访问外部类成员
        }
    }
    
    // 静态内部类
    static class StaticInner {
        public void method() {
            // System.out.println(x);  // 不能访问非静态成员
            System.out.println("静态内部类");
        }
    }
    
    public void outerMethod() {
        // 局部内部类
        class LocalInner {
            public void method() {
                System.out.println(x);
            }
        }
        LocalInner local = new LocalInner();
        local.method();
    }
    
    public void test() {
        // 匿名内部类
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类");
            }
        };
        new Thread(r).start();
        
        // Lambda简化
        new Thread(() -> System.out.println("Lambda")).start();
    }
}

内部类特点:

类型 特点
成员内部类 可以访问外部类所有成员,依赖外部类实例
静态内部类 只能访问外部类静态成员,不依赖外部类实例
局部内部类 定义在方法内,作用域限定于方法
匿名内部类 没有名字,只能实例化一次

第三部分:Java集合

3.1 集合框架概述

Q25: Java集合框架的结构?

Collection(单列集合)
├── List(有序、可重复)
│   ├── ArrayList
│   ├── LinkedList
│   └── Vector
│       └── Stack
├── Set(无序、不可重复)
│   ├── HashSet
│   │   └── LinkedHashSet
│   └── TreeSet
└── Queue(队列)
    ├── LinkedList
    ├── PriorityQueue
    └── ArrayDeque

Map(双列集合)
├── HashMap
│   └── LinkedHashMap
├── TreeMap
├── Hashtable
│   └── Properties
└── ConcurrentHashMap

3.2 List集合

Q26: ArrayList和LinkedList的区别?

特性 ArrayList LinkedList
底层结构 动态数组 双向链表
随机访问 O(1) O(n)
插入删除 O(n) O(1)(已知位置)
内存占用 连续空间 节点开销大
扩容 1.5倍 无需扩容
适用场景 查询多 增删多
// ArrayList扩容源码
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);  // 1.5倍
    // ...
}

// LinkedList节点
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
}

Q27: Vector和ArrayList的区别?

特性 ArrayList Vector
线程安全 不安全 安全(synchronized)
性能 高 低
扩容 1.5倍 2倍
使用场景 单线程 多线程(已过时,用CopyOnWriteArrayList)

Q28: List的遍历方式?

List<String> list = Arrays.asList("a", "b", "c");

// 1. for循环
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 2. 增强for循环
for (String s : list) {
    System.out.println(s);
}

// 3. Iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

// 4. forEach + Lambda
list.forEach(System.out::println);

// 5. Stream
list.stream().forEach(System.out::println);

// 6. ListIterator(双向遍历)
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
    System.out.println(lit.next());
}
while (lit.hasPrevious()) {
    System.out.println(lit.previous());
}

Q29: 遍历时删除元素的正确方式?

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));

// ✗ 错误:ConcurrentModificationException
for (Integer i : list) {
    if (i == 3) list.remove(i);
}

// ✗ 错误:跳过元素
for (int i = 0; i < list.size(); i++) {
    if (list.get(i) == 3) list.remove(i);
}

// ✓ 正确:Iterator
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    if (it.next() == 3) it.remove();
}

// ✓ 正确:倒序遍历
for (int i = list.size() - 1; i >= 0; i--) {
    if (list.get(i) == 3) list.remove(i);
}

// ✓ 正确:removeIf(JDK 8+)
list.removeIf(i -> i == 3);

// ✓ 正确:CopyOnWriteArrayList
CopyOnWriteArrayList<Integer> copyList = new CopyOnWriteArrayList<>(list);
for (Integer i : copyList) {
    if (i == 3) copyList.remove(i);
}

3.3 Set集合

Q30: HashSet、LinkedHashSet、TreeSet的区别?

特性 HashSet LinkedHashSet TreeSet
底层 哈希表 哈希表+链表 红黑树
有序性 无序 插入顺序 排序顺序
性能 O(1) O(1) O(log n)
null 允许 允许 不允许
// HashSet
Set<String> hashSet = new HashSet<>();
hashSet.add("c");
hashSet.add("a");
hashSet.add("b");
System.out.println(hashSet);  // [a, b, c] 顺序不确定

// LinkedHashSet(保持插入顺序)
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("c");
linkedHashSet.add("a");
linkedHashSet.add("b");
System.out.println(linkedHashSet);  // [c, a, b]

// TreeSet(排序)
Set<String> treeSet = new TreeSet<>();
treeSet.add("c");
treeSet.add("a");
treeSet.add("b");
System.out.println(treeSet);  // [a, b, c]

Q31: HashSet如何保证元素唯一?

// 添加元素时的判断逻辑
public boolean add(E e) {
    return map.put(e, PRESENT) == null;
}

// HashMap的put方法
public V put(K key, V value) {
    // 1. 计算hashCode
    int hash = key.hashCode();
    
    // 2. 计算索引位置
    int index = hash % table.length;
    
    // 3. 遍历该位置的链表
    for (Node<K,V> node : table[index]) {
        // 4. 比较hashCode和equals
        if (node.hash == hash && 
            (node.key == key || key.equals(node.key))) {
            return node.value;  // 已存在,不添加
        }
    }
    
    // 5. 不存在,添加新节点
    addNode(hash, key, value, index);
    return null;
}

结论:先比较hashCode,再比较equals。都相同才认为是重复元素。

Q32: TreeSet的排序方式?

// 1. 自然排序(实现Comparable)
class Person implements Comparable<Person> {
    private String name;
    private int age;
    
    @Override
    public int compareTo(Person o) {
        return this.age - o.age;  // 按年龄排序
    }
}

Set<Person> set1 = new TreeSet<>();
set1.add(new Person("Tom", 20));
set1.add(new Person("Jerry", 18));

// 2. 定制排序(Comparator)
Set<Person> set2 = new TreeSet<>((p1, p2) -> p1.getName().compareTo(p2.getName()));

// 或
Set<Person> set3 = new TreeSet<>(Comparator.comparing(Person::getName));

// 3. 多字段排序
Set<Person> set4 = new TreeSet<>(
    Comparator.comparing(Person::getAge)
              .thenComparing(Person::getName)
);

3.4 Map集合

Q33: HashMap的底层结构?

JDK 1.7:数组 + 链表

JDK 1.8:数组 + 链表 + 红黑树

// HashMap的关键属性
public class HashMap<K,V> {
    // 默认初始容量 16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    
    // 最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    
    // 默认负载因子 0.75
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    // 链表转红黑树阈值
    static final int TREEIFY_THRESHOLD = 8;
    
    // 红黑树转链表阈值
    static final int UNTREEIFY_THRESHOLD = 6;
    
    // 转红黑树时数组最小容量
    static final int MIN_TREEIFY_CAPACITY = 64;
    
    // 哈希桶数组
    transient Node<K,V>[] table;
    
    // 当前元素个数
    transient int size;
    
    // 扩容阈值 = 容量 * 负载因子
    int threshold;
    
    // 负载因子
    final float loadFactor;
}

Q34: HashMap的扩容机制?

// 扩容条件:size > threshold(容量 * 负载因子)
// 扩容大小:原来的2倍

void resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = oldTab.length;
    int newCap = oldCap << 1;  // 2倍扩容
    
    // 创建新数组
    Node<K,V>[] newTab = new Node[newCap];
    
    // 重新计算位置并迁移元素
    for (int j = 0; j < oldCap; ++j) {
        Node<K,V> e;
        if ((e = oldTab[j]) != null) {
            oldTab[j] = null;
            if (e.next == null) {
                // 单个节点,直接重新计算位置
                newTab[e.hash & (newCap - 1)] = e;
            } else if (e instanceof TreeNode) {
                // 红黑树拆分
                ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
            } else {
                // 链表拆分:原位置或原位置+oldCap
                Node<K,V> loHead = null, loTail = null;
                Node<K,V> hiHead = null, hiTail = null;
                do {
                    if ((e.hash & oldCap) == 0) {
                        // 原位置
                        if (loTail == null) loHead = e;
                        else loTail.next = e;
                        loTail = e;
                    } else {
                        // 原位置 + oldCap
                        if (hiTail == null) hiHead = e;
                        else hiTail.next = e;
                        hiTail = e;
                    }
                } while ((e = e.next) != null);
            }
        }
    }
}

Q35: HashMap为什么是线程不安全的?

// 1. JDK 1.7:扩容时链表死循环
// 多线程扩容时,链表迁移可能导致环形链表

// 2. JDK 1.8:数据丢失
// 多线程put时,同时判断同一位置为空,后者覆盖前者

// 3. size统计不准确
// ++modCount; ++size 不是原子操作

// 解决方案
// 方案1:Collections.synchronizedMap
Map<String, String> map1 = Collections.synchronizedMap(new HashMap<>());

// 方案2:ConcurrentHashMap(推荐)
Map<String, String> map2 = new ConcurrentHashMap<>();

// 方案3:Hashtable(不推荐,性能差)
Map<String, String> map3 = new Hashtable<>();

Q36: ConcurrentHashMap的实现原理?

JDK 1.7:

  • 分段锁(Segment数组)
  • 每个Segment继承ReentrantLock
  • 并发度 = Segment个数

JDK 1.8:

  • 数组 + 链表 + 红黑树
  • CAS + synchronized(锁单个桶)
  • 锁粒度更细,性能更高
// JDK 1.8 put方法核心逻辑
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 1. 计算hash
    int hash = spread(key.hashCode());
    
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        
        // 2. 数组为空,初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        
        // 3. 目标桶为空,CAS插入
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;
        }
        
        // 4. 正在扩容,帮忙迁移
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        
        // 5. 插入链表或红黑树
        else {
            synchronized (f) {
                // 遍历链表/红黑树,插入或更新
                // ...
            }
        }
    }
    
    // 6. 检查是否需要树化
    addCount(1L, binCount);
    return null;
}

Q37: HashMap和Hashtable的区别?

特性 HashMap Hashtable
线程安全 不安全 安全(synchronized)
null键值 允许 不允许
继承 AbstractMap Dictionary
初始容量 16 11
扩容 2倍 2倍+1
哈希计算 异或高低位 直接取模
推荐 ✓ ✗(用ConcurrentHashMap)

3.5 集合工具类

Q38: Collections常用方法?

List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5));

// 排序
Collections.sort(list);                    // [1, 1, 3, 4, 5]
Collections.sort(list, Comparator.reverseOrder());  // 降序

// 二分查找(必须先排序)
int index = Collections.binarySearch(list, 3);

// 最大最小
Integer max = Collections.max(list);
Integer min = Collections.min(list);

// 反转
Collections.reverse(list);

// 随机打乱
Collections.shuffle(list);

// 填充
Collections.fill(list, 0);

// 复制
List<Integer> copy = new ArrayList<>(Collections.nCopies(5, 0));
Collections.copy(copy, list);

// 不可变集合
List<Integer> unmodifiable = Collections.unmodifiableList(list);

// 同步集合
List<Integer> synchronizedList = Collections.synchronizedList(list);

// 单元素集合
List<Integer> singleton = Collections.singletonList(1);

第四部分:Java多线程与并发

4.1 线程基础

Q39: 创建线程的方式?

// 方式1:继承Thread类
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running");
    }
}
new MyThread().start();

// 方式2:实现Runnable接口
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable running");
    }
}
new Thread(new MyRunnable()).start();

// 方式3:实现Callable接口(有返回值)
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Callable result";
    }
}
FutureTask<String> future = new FutureTask<>(new MyCallable());
new Thread(future).start();
String result = future.get();  // 阻塞获取结果

// 方式4:线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> System.out.println("ThreadPool running"));
executor.shutdown();

// Lambda简化
new Thread(() -> System.out.println("Lambda")).start();

Q40: 线程的生命周期?

        ┌────────────────────────────────────────┐
        │                                        │
        ▼                                        │
┌─────────┐  start()  ┌──────────┐              │
│   NEW   │ ────────> │ RUNNABLE │              │
└─────────┘           └──────────┘              │
                          │ │ │                  │
         ┌────────────────┘ │ └──────────┐      │
         │                  │             │      │
         ▼                  ▼             ▼      │
┌──────────┐        ┌──────────┐   ┌──────────┐ │
│  BLOCKED │        │ WAITING  │   │TIMED_    │ │
│(等待锁)   │        │(无限等待)│   │WAITING   │ │
└──────────┘        └──────────┘   │(有限等待) │ │
     │                    │         └──────────┘ │
     │                    │              │       │
     └────────────────────┴──────────────┘       │
                          │                      │
                          ▼                      │
                    ┌─────────────┐              │
                    │ TERMINATED  │◄─────────────┘
                    └─────────────┘
// 状态转换示例
Thread thread = new Thread(() -> {
    try {
        Thread.sleep(1000);        // TIMED_WAITING
        synchronized (Object.class) {
            Object.class.wait();   // WAITING
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});

System.out.println(thread.getState());  // NEW
thread.start();
System.out.println(thread.getState());  // RUNNABLE

Q41: sleep()和wait()的区别?

特性 sleep() wait()
所属类 Thread Object
是否释放锁 否 是
使用位置 任意 同步代码块
唤醒方式 超时自动唤醒 notify/notifyAll或超时
用途 暂停执行 线程通信
// sleep:不释放锁
synchronized (lock) {
    Thread.sleep(1000);  // 休眠1秒,但持有锁
    // 其他线程无法获取锁
}

// wait:释放锁
synchronized (lock) {
    lock.wait();  // 释放锁并等待
    // 其他线程可以获取锁
    // 被notify唤醒后,重新获取锁继续执行
}

Q42: start()和run()的区别?

Thread thread = new Thread(() -> {
    System.out.println(Thread.currentThread().getName());
});

thread.run();   // 主线程执行,输出:main
thread.start(); // 新线程执行,输出:Thread-0

// start()源码
public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    nativeCreate(this, stackSize, daemon);  // 调用本地方法创建新线程
}

// start()会调用run(),但在新线程中执行

4.2 线程同步

Q43: synchronized的使用方式?

public class SynchronizedDemo {
    private final Object lock = new Object();
    private static int count = 0;
    
    // 1. 同步实例方法(锁当前实例this)
    public synchronized void instanceMethod() {
        // 临界区
    }
    
    // 2. 同步静态方法(锁Class对象)
    public static synchronized void staticMethod() {
        // 临界区
    }
    
    // 3. 同步代码块(锁指定对象)
    public void blockMethod() {
        synchronized (lock) {
            // 临界区
        }
    }
    
    // 4. 同步代码块(锁当前实例)
    public void blockThis() {
        synchronized (this) {
            // 等同于同步实例方法
        }
    }
    
    // 5. 同步代码块(锁Class对象)
    public void blockClass() {
        synchronized (SynchronizedDemo.class) {
            // 等同于同步静态方法
        }
    }
}

Q44: synchronized的锁升级过程?

无锁 → 偏向锁 → 轻量级锁 → 重量级锁
锁状态 适用场景 性能
无锁 无竞争 最高
偏向锁 单线程重入 高
轻量级锁 交替执行 中
重量级锁 竞争激烈 低
// 对象头中的Mark Word结构(64位JVM)
// | 锁状态 | 存储内容 |
// |--------|----------|
// | 无锁 | hashCode, age, 01 |
// | 偏向锁 | 线程ID, epoch, age, 01 |
// | 轻量级锁 | 指向栈中锁记录的指针, 00 |
// | 重量级锁 | 指向互斥量的指针, 10 |

// 锁升级过程
// 1. 偏向锁:第一个线程访问时,在对象头记录线程ID
// 2. 轻量级锁:其他线程竞争时,CAS尝试获取,失败则自旋
// 3. 重量级锁:自旋超过阈值,升级为重量级锁,线程阻塞

Q45: synchronized和Lock的区别?

特性 synchronized Lock
层次 JVM关键字 Java接口
获取锁 自动 手动lock()
释放锁 自动 手动unlock()
可中断 否 lockInterruptibly()
公平性 非公平 可选公平/非公平
条件变量 单一 多个Condition
尝试获取锁 否 tryLock()
性能 JDK6后优化,差距不大 高竞争时更好
// Lock标准用法
Lock lock = new ReentrantLock();
try {
    lock.lock();  // 获取锁
    // 临界区
} finally {
    lock.unlock();  // 必须释放锁
}

// 尝试获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
} else {
    // 获取失败
}

// 可中断获取锁
try {
    lock.lockInterruptibly();
    // 临界区
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
} finally {
    lock.unlock();
}

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

// Condition(条件变量)
ReentrantLock reLock = new ReentrantLock();
Condition condition = reLock.newCondition();
// await() / signal() / signalAll()

4.3 volatile关键字

Q46: volatile的作用?

public class VolatileDemo {
    // 1. 可见性
    private volatile boolean running = true;
    
    public void stop() {
        running = false;  // 对其他线程立即可见
    }
    
    public void doWork() {
        while (running) {
            // 工作内容
        }
    }
    
    // 2. 禁止指令重排序
    private volatile Singleton instance;
    
    public Singleton getInstance() {
        if (instance == null) {               // 1
            synchronized (VolatileDemo.class) {
                if (instance == null) {
                    instance = new Singleton(); // 2
                    // 不使用volatile可能重排序:
                    // memory = allocate();
                    // instance = memory;      // 先赋值
                    // ctorInstance(memory);  // 后初始化
                    // 其他线程可能看到未初始化完成的对象
                }
            }
        }
        return instance;
    }
}

Q47: volatile为什么不保证原子性?

public class VolatileAtomic {
    private volatile int count = 0;
    
    public void increment() {
        count++;  // 非原子操作
        // 实际是3步:
        // 1. 读取count
        // 2. count + 1
        // 3. 写入count
    }
    
    // 多线程测试
    public static void main(String[] args) throws InterruptedException {
        VolatileAtomic demo = new VolatileAtomic();
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.increment();
                }
            });
        }
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
        
        System.out.println(demo.count);  // 结果 < 10000
    }
}

解决方案:

// 1. synchronized
public synchronized void increment() { count++; }

// 2. AtomicInteger
private AtomicInteger count = new AtomicInteger(0);
public void increment() { count.incrementAndGet(); }

// 3. ReentrantLock
private final Lock lock = new ReentrantLock();
public void increment() {
    lock.lock();
    try { count++; } finally { lock.unlock(); }
}

4.4 CAS与原子类

Q48: CAS是什么?

Compare And Swap(比较并交换):乐观锁实现

// CAS操作原理(伪代码)
public boolean compareAndSwap(int expected, int newValue) {
    if (this.value == expected) {  // 比较
        this.value = newValue;      // 交换
        return true;
    }
    return false;
}

// AtomicInteger源码
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

// Unsafe类中的CAS
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);  // 读取当前值
    } while (!compareAndSwapInt(o, offset, v, v + delta));  // CAS失败则重试
    return v;
}

Q49: CAS的ABA问题?

// ABA问题场景
// 线程1: 读取A → 准备CAS(A, B)
// 线程2: A → C → A
// 线程1: CAS成功,但实际上中间被修改过

// 解决方案:版本号/时间戳
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(1, 0);

// 更新时携带版本号
int stamp = ref.getStamp();
ref.compareAndSet(1, 2, stamp, stamp + 1);

// AtomicMarkableReference(boolean标记)
AtomicMarkableReference<Integer> markRef = new AtomicMarkableReference<>(1, false);

Q50: JUC原子类有哪些?

Atomic类
├── 基本类型
│   ├── AtomicInteger
│   ├── AtomicLong
│   └── AtomicBoolean
├── 数组类型
│   ├── AtomicIntegerArray
│   ├── AtomicLongArray
│   └── AtomicReferenceArray
├── 引用类型
│   ├── AtomicReference
│   ├── AtomicStampedReference
│   └── AtomicMarkableReference
└── 字段更新器
    ├── AtomicIntegerFieldUpdater
    ├── AtomicLongFieldUpdater
    └── AtomicReferenceFieldUpdater
// 常用方法
AtomicInteger atomic = new AtomicInteger(0);

atomic.get();                        // 获取值
atomic.set(10);                      // 设置值
atomic.getAndSet(10);                // 获取并设置
atomic.compareAndSet(0, 10);         // CAS
atomic.getAndIncrement();            // 获取并自增
atomic.incrementAndGet();            // 自增并获取
atomic.getAndAdd(5);                 // 获取并加
atomic.addAndGet(5);                 // 加并获取
atomic.updateAndGet(x -> x * 2);     // 原子更新

// LongAdder(高并发累加)
LongAdder adder = new LongAdder();
adder.increment();
adder.add(10);
long sum = adder.sum();  // 最终一致性

4.5 AQS

Q51: AQS是什么?

AbstractQueuedSynchronizer:抽象队列同步器,JUC核心基础

// AQS核心设计
public abstract class AbstractQueuedSynchronizer {
    // 同步状态(volatile)
    private volatile int state;
    
    // CLH队列头节点
    private transient volatile Node head;
    
    // CLH队列尾节点
    private transient volatile Node tail;
    
    // 队列节点
    static final class Node {
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;  // 等待的线程
        volatile int waitStatus; // 等待状态
    }
}

核心原理:

  1. state:同步状态,0表示未占用,>0表示占用
  2. CLH队列:双向链表,存储等待线程
  3. 独占模式:只有一个线程能获取锁(ReentrantLock)
  4. 共享模式:多个线程可同时获取锁(CountDownLatch)

Q52: AQS的应用?

类 模式 说明
ReentrantLock 独占 可重入锁
ReentrantReadWriteLock 独占+共享 读写锁
CountDownLatch 共享 倒计时器
Semaphore 共享 信号量
CyclicBarrier 共享 循环栅栏
// 简单实现独占锁
class SimpleLock extends AbstractQueuedSynchronizer {
    @Override
    protected boolean tryAcquire(int arg) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    
    @Override
    protected boolean tryRelease(int arg) {
        setState(0);
        setExclusiveOwnerThread(null);
        return true;
    }
    
    public void lock() { acquire(1); }
    public void unlock() { release(1); }
}

4.6 线程池

Q53: 线程池核心参数?

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

执行流程:

提交任务
    │
    ▼
核心线程数未满? ───是──→ 创建核心线程执行
    │
    否
    ▼
工作队列未满? ───是──→ 加入队列等待
    │
    否
    ▼
最大线程数未满? ───是──→ 创建非核心线程执行
    │
    否
    ▼
执行拒绝策略

Q54: 线程池的工作队列?

队列 特点 适用场景
ArrayBlockingQueue 有界数组队列 防止资源耗尽
LinkedBlockingQueue 可选有界链表队列 默认无界(危险)
SynchronousQueue 不存储元素 直接传递,高吞吐
PriorityBlockingQueue 无界优先队列 按优先级执行
DelayQueue 无界延迟队列 定时任务

Q55: 线程池的拒绝策略?

策略 行为
AbortPolicy(默认) 抛出RejectedExecutionException
CallerRunsPolicy 由调用线程执行任务
DiscardPolicy 直接丢弃,不抛异常
DiscardOldestPolicy 丢弃队列最老的任务,重新提交
// 自定义拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

// 或自定义
executor.setRejectedExecutionHandler((r, e) -> {
    // 记录日志
    log.warn("任务被拒绝: " + r.toString());
    // 存储到数据库
    saveToDatabase(r);
});

Q56: 如何合理配置线程池?

// CPU密集型
int cpuCount = Runtime.getRuntime().availableProcessors();
int poolSize = cpuCount + 1;

// IO密集型
int poolSize = cpuCount * 2;

// 混合型
int poolSize = cpuCount * (1 + 等待时间/计算时间);

// 示例配置
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    8,                          // 核心线程 = CPU核心数
    16,                         // 最大线程
    60L, TimeUnit.SECONDS,      // 空闲时间
    new ArrayBlockingQueue<>(200),  // 有界队列
    new ThreadFactoryBuilder().setNameFormat("worker-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

// 监控线程池
executor.getActiveCount();      // 活动线程数
executor.getQueue().size();     // 队列任务数
executor.getCompletedTaskCount(); // 已完成任务数
executor.getPoolSize();         // 当前线程数

Q57: Executors创建线程池的问题?

// 问题1:FixedThreadPool/SingleThreadPool
// 队列无界,可能OOM
ExecutorService fixed = Executors.newFixedThreadPool(10);
// 使用LinkedBlockingQueue(无界)

// 问题2:CachedThreadPool
// 最大线程数Integer.MAX_VALUE,可能OOM
ExecutorService cached = Executors.newCachedThreadPool();
// 使用SynchronousQueue,线程数无限

// 问题3:ScheduledThreadPool
// 最大线程数Integer.MAX_VALUE
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(10);

// 正确做法:手动创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(queueCapacity),
    new CustomThreadFactory(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

4.7 并发工具类

Q58: CountDownLatch的使用?

// 场景:等待多个线程完成
CountDownLatch latch = new CountDownLatch(3);

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            // 执行任务
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " 完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown();  // 计数减1
        }
    }).start();
}

latch.await();  // 阻塞等待计数归零
System.out.println("所有任务完成");

// 超时等待
boolean completed = latch.await(5, TimeUnit.SECONDS);

Q59: CyclicBarrier的使用?

// 场景:多个线程互相等待,一起出发
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程就绪,开始执行");
});

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + " 就绪");
            barrier.await();  // 等待其他线程
            System.out.println(Thread.currentThread().getName() + " 执行");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
}

// 可重用
barrier.reset();

Q60: Semaphore的使用?

// 场景:限流,控制并发数
Semaphore semaphore = new Semaphore(3);  // 同时最多3个线程

for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire();  // 获取许可
            System.out.println(Thread.currentThread().getName() + " 执行");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();  // 释放许可
        }
    }).start();
}

// 尝试获取
boolean acquired = semaphore.tryAcquire(1, TimeUnit.SECONDS);

Q61: CountDownLatch vs CyclicBarrier?

特性 CountDownLatch CyclicBarrier
计数方向 递减 递增
可重用 否 是
触发条件 计数归零 到达指定数量
使用场景 等待一组线程完成 一组线程互相等待

4.8 ThreadLocal

Q62: ThreadLocal的原理?

// 每个Thread对象持有ThreadLocalMap
public class Thread {
    ThreadLocal.ThreadLocalMap threadLocals;
}

// ThreadLocalMap是ThreadLocal的内部类
// Entry的key是ThreadLocal对象,value是线程本地值
static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

原理图解:

Thread
  └── ThreadLocalMap
        ├── Entry[key=ThreadLocal1, value=Value1]
        ├── Entry[key=ThreadLocal2, value=Value2]
        └── Entry[key=ThreadLocal3, value=Value3]

Q63: ThreadLocal的使用场景?

// 场景1:数据库连接
private static ThreadLocal<Connection> connectionHolder = 
    ThreadLocal.withInitial(() -> null);

public static Connection getConnection() {
    Connection conn = connectionHolder.get();
    if (conn == null) {
        conn = DriverManager.getConnection(url, user, password);
        connectionHolder.set(conn);
    }
    return conn;
}

// 场景2:日期格式化
private static ThreadLocal<SimpleDateFormat> dateFormat = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

public String formatDate(Date date) {
    return dateFormat.get().format(date);
}

// 场景3:用户上下文
public class UserContext {
    private static ThreadLocal<User> userHolder = new ThreadLocal<>();
    
    public static void setUser(User user) {
        userHolder.set(user);
    }
    
    public static User getUser() {
        return userHolder.get();
    }
    
    public static void clear() {
        userHolder.remove();  // 重要:防止内存泄漏
    }
}

Q64: ThreadLocal的内存泄漏问题?

// 问题:Entry的key是弱引用,value是强引用
// key被回收后,value无法被访问,但不会被回收

// 解决方案:使用后调用remove()
try {
    threadLocal.set(value);
    // 业务逻辑
} finally {
    threadLocal.remove();  // 必须清理
}

// ThreadLocalMap也会在get/set时清理过期Entry
// 但最好手动remove,避免等待

第五部分:JVM虚拟机

5.1 JVM内存结构

Q65: JVM运行时数据区?

┌─────────────────────────────────────────────────────┐
│                   JVM运行时数据区                      │
├─────────────┬─────────────┬─────────────────────────┤
│   线程私有   │   线程私有   │       线程共享          │
├─────────────┼─────────────┼───────────┬─────────────┤
│  程序计数器  │  虚拟机栈   │    堆     │   方法区    │
│  (PC)       │  (Stack)   │  (Heap)   │ (Metaspace) │
├─────────────┼─────────────┼───────────┴─────────────┤
│  本地方法栈  │             │                         │
│ (Native)    │             │                         │
└─────────────┴─────────────┴─────────────────────────┘
区域 作用 异常
程序计数器 当前执行的字节码行号 无
虚拟机栈 方法调用、局部变量 StackOverflowError、OOM
本地方法栈 Native方法服务 StackOverflowError、OOM
堆 对象实例 OutOfMemoryError
方法区 类信息、常量、静态变量 OutOfMemoryError

Q66: 堆内存结构?

┌─────────────────────────────────────────────┐
│                   堆(Heap)                   │
├───────────────────┬─────────────────────────┤
│      新生代        │         老年代          │
│  (Young Generation)│   (Old Generation)      │
├─────────┬─────────┤                         │
│   Eden  │ Survivor│                         │
│         │  S0  S1 │                         │
│   8     │  1   1  │                         │
│  (80%)  │ (10%)   │        (2/3)            │
└─────────┴─────────┴─────────────────────────┘
       新生代占1/3,老年代占2/3
// JVM参数
-Xms1024m    // 堆初始大小
-Xmx1024m    // 堆最大大小
-Xmn512m     // 新生代大小
-XX:SurvivorRatio=8  // Eden:Survivor = 8:1:1
-XX:NewRatio=2       // 新生代:老年代 = 1:2

Q67: 方法区的演变?

JDK版本 方法区实现
JDK 7及之前 永久代(PermGen),JVM内存
JDK 8及之后 元空间(Metaspace),本地内存
// JDK 8参数
-XX:MetaspaceSize=128m      // 初始大小
-XX:MaxMetaspaceSize=256m   // 最大大小

// 运行时常量池
// JDK 7:从方法区移到堆
// JDK 8:在堆中

// 字符串常量池
// JDK 7:从方法区移到堆
String.intern();  // 如果池中没有,放入池中并返回引用

5.2 垃圾回收

Q68: 如何判断对象可回收?

// 1. 引用计数法(Python使用)
// 问题:循环引用无法回收

// 2. 可达性分析(Java使用)
// 从GC Roots开始向下搜索,不可达的对象可回收

// GC Roots包括:
// - 虚拟机栈中的引用
// - 方法区静态属性引用
// - 方法区常量引用
// - 本地方法栈JNI引用
// - 同步锁持有的对象
// - JVM内部引用(基本类型Class对象等)

Q69: Java的引用类型?

引用类型 回收时机 用途
强引用 永不回收(除非不可达) 普通对象
软引用 内存不足时 缓存
弱引用 下次GC WeakHashMap
虚引用 随时可能 跟踪回收
// 强引用
Object strong = new Object();

// 软引用
SoftReference<Object> soft = new SoftReference<>(new Object());
Object obj = soft.get();  // 获取对象,可能为null

// 弱引用
WeakReference<Object> weak = new WeakReference<>(new Object());
Object obj = weak.get();  // 获取对象,可能为null

// 虚引用
PhantomReference<Object> phantom = new PhantomReference<>(
    new Object(), new ReferenceQueue<>());
Object obj = phantom.get();  // 总是返回null

Q70: 垃圾回收算法?

算法 原理 优点 缺点 适用
标记-清除 标记后清除 简单 内存碎片 老年代
复制算法 分两块,存活复制 无碎片 内存利用率低 新生代
标记-整理 标记后移动到一端 无碎片 效率低 老年代
分代收集 新老年代不同算法 综合 复杂 主流
// 新生代:复制算法
// Eden + Survivor0 + Survivor1
// 新对象在Eden分配
// GC时,存活对象复制到另一Survivor

// 老年代:标记-清除或标记-整理
// 大对象直接进入老年代
// 长期存活对象进入老年代

Q71: 常见垃圾收集器?

收集器 类型 算法 特点
Serial 新生代 复制 单线程,简单高效
ParNew 新生代 复制 多线程Serial
Parallel Scavenge 新生代 复制 吞吐量优先
Serial Old 老年代 标记-整理 单线程
Parallel Old 老年代 标记-整理 多线程
CMS 老年代 标记-清除 低停顿
G1 新老年代 复制+标记-整理 分区收集
ZGC 全堆 复制 低延迟(JDK11+)
// 常用组合
// Serial + Serial Old:单线程应用
// ParNew + CMS:低停顿
// Parallel Scavenge + Parallel Old:吞吐量
// G1:大内存、可预测停顿

// 参数
-XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
-XX:+UseZGC

Q72: CMS收集器的工作流程?

1. 初始标记(STW):标记GC Roots直接关联的对象
2. 并发标记:从GC Roots遍历整个对象图
3. 重新标记(STW):修正并发标记期间的变动
4. 并发清除:清除标记的对象

特点:

  • 并发收集,低停顿
  • 标记-清除,有碎片
  • CPU敏感
  • 浮动垃圾问题

Q73: G1收集器的特点?

┌────┬────┬────┬────┐
│ E  │ S  │    │    │  E = Eden
├────┼────┼────┼────┤  S = Survivor
│    │ O  │ O  │ O  │  O = Old
├────┼────┼────┼────┤  H = Humongous
│    │ H  │    │    │
├────┼────┼────┼────┤
│    │    │    │    │
└────┴────┴────┴────┘
       Region分区

特点:

  • 分区收集
  • 可预测停顿
  • 无内存碎片
  • 大对象特殊处理

模式:

  • Young GC:新生代收集
  • Mixed GC:新生代+部分老年代
  • Full GC:全面收集(应避免)

5.3 类加载

Q74: 类加载过程?

加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
  │      │      │      │      │
  │      │      │      │      └─ 执行<clinit>,静态变量赋值、静态代码块
  │      │      │      └──────── 符号引用 → 直接引用
  │      │      └─────────────── 为静态变量分配内存,设置默认值
  │      └────────────────────── 校验字节码正确性
  └───────────────────────────── 读取class文件,创建Class对象
public class ClassInitDemo {
    private static int value = 123;  // 准备阶段:value=0,初始化阶段:value=123
    
    static {
        System.out.println("静态代码块");
    }
    
    // <clinit>方法按顺序执行静态变量赋值和静态代码块
}

Q75: 类加载器有哪些?

Bootstrap ClassLoader(启动类加载器)
    ↑ 加载JAVA_HOME/lib/*.jar
    │
Extension ClassLoader(扩展类加载器)
    ↑ 加载JAVA_HOME/lib/ext/*.jar
    │
Application ClassLoader(应用类加载器)
    ↑ 加载classpath下的类
    │
Custom ClassLoader(自定义类加载器)

Q76: 双亲委派模型?

// 工作流程
protected Class<?> loadClass(String name, boolean resolve) {
    // 1. 检查是否已加载
    Class<?> c = findLoadedClass(name);
    
    if (c == null) {
        try {
            if (parent != null) {
                // 2. 委派父类加载器加载
                c = parent.loadClass(name, false);
            } else {
                // 3. 委派启动类加载器加载
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // 父类无法加载
        }
        
        if (c == null) {
            // 4. 自己加载
            c = findClass(name);
        }
    }
    return c;
}

好处:

  1. 避免类重复加载
  2. 保护核心类安全(防止自定义java.lang.String)

破坏双亲委派:

  • JDBC:DriverManager使用线程上下文类加载器
  • Tomcat:每个WebApp独立类加载器
  • OSGi:模块化加载

5.4 JVM调优

Q77: 常见JVM参数?

# 堆内存
-Xms512m              # 堆初始大小
-Xmx2g                # 堆最大大小
-Xmn256m              # 新生代大小
-Xss256k              # 线程栈大小

# 方法区(JDK8)
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m

# 垃圾收集器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200  # 最大停顿时间

# GC日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log

# OOM时导出堆快照
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=dump.hprof

Q78: 常见OOM及解决?

// 1. Java heap space
// 原因:对象太多,堆不够
// 解决:增大-Xmx,检查内存泄漏

// 2. GC overhead limit exceeded
// 原因:GC花费过多时间但回收很少
// 解决:增大堆,优化代码

// 3. Metaspace
// 原因:类太多,元空间不够
// 解决:增大-XX:MaxMetaspaceSize

// 4. Unable to create new native thread
// 原因:线程数过多
// 解决:减少线程数,增大系统限制

// 5. Out of memory: Java heap space
// 排查:导出hprof文件,使用MAT或jvisualvm分析

Q79: JVM监控工具?

# 命令行工具
jps                  # 查看Java进程
jstat -gc <pid>      # GC统计
jinfo <pid>          # JVM参数
jmap -histo <pid>    # 对象统计
jmap -dump:format=b,file=dump.hprof <pid>  # 导出堆快照
jstack <pid>         # 线程栈

# GUI工具
jconsole             # JMX监控
jvisualvm            # 综合分析
VisualVM             # jvisualvm升级版
MAT                  # 内存分析
Arthas               # 阿里开源诊断工具

第六部分:Spring框架

6.1 Spring核心

Q80: 什么是IOC?

Inversion of Control(控制反转):对象的创建和管理交给Spring容器

// 传统方式:手动创建
UserService userService = new UserServiceImpl();

// Spring方式:依赖注入
@Service
public class UserController {
    @Autowired
    private UserService userService;  // Spring自动注入
}

// 或构造器注入(推荐)
@Service
public class UserController {
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
}

Q81: BeanFactory和ApplicationContext的区别?

特性 BeanFactory ApplicationContext
初始化 延迟初始化 容器启动时初始化
国际化 不支持 支持
事件机制 不支持 支持
AOP 不支持 支持
使用场景 资源受限 企业应用
// BeanFactory
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
UserService userService = (UserService) factory.getBean("userService");

// ApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean(UserService.class);

Q82: Spring Bean的作用域?

作用域 说明
singleton 单例(默认)
prototype 每次获取创建新对象
request HTTP请求
session HTTP会话
application ServletContext生命周期
websocket WebSocket生命周期
@Service
@Scope("prototype")
public class PrototypeBean {}

Q83: Spring Bean的生命周期?

1. 实例化(Instantiation)
   ├── 构造器
   └── BeanPostProcessor.postProcessBeforeInstantiation

2. 属性赋值(Populate)
   └── 依赖注入

3. 初始化(Initialization)
   ├── BeanPostProcessor.postProcessBeforeInitialization
   ├── @PostConstruct
   ├── InitializingBean.afterPropertiesSet
   ├── init-method
   └── BeanPostProcessor.postProcessAfterInitialization

4. 使用(Use)

5. 销毁(Destruction)
   ├── @PreDestroy
   ├── DisposableBean.destroy
   └── destroy-method

6.2 AOP

Q84: 什么是AOP?

Aspect Oriented Programming(面向切面编程):将横切关注点模块化

// 核心概念
// - 切面(Aspect):横切关注点的模块化
// - 切入点(Pointcut):在哪些地方执行
// - 通知(Advice):在什么时候执行
// - 连接点(Joinpoint):程序执行的特定点

@Aspect
@Component
public class LoggingAspect {
    
    // 切入点表达式
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethod() {}
    
    // 前置通知
    @Before("serviceMethod()")
    public void before(JoinPoint joinPoint) {
        System.out.println("Before: " + joinPoint.getSignature());
    }
    
    // 后置通知
    @AfterReturning(pointcut = "serviceMethod()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("After returning: " + result);
    }
    
    // 异常通知
    @AfterThrowing(pointcut = "serviceMethod()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        System.out.println("After throwing: " + ex);
    }
    
    // 最终通知
    @After("serviceMethod()")
    public void after(JoinPoint joinPoint) {
        System.out.println("After: " + joinPoint.getSignature());
    }
    
    // 环绕通知
    @Around("serviceMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Around before");
        Object result = joinPoint.proceed();  // 执行目标方法
        System.out.println("Around after");
        return result;
    }
}

Q85: 切入点表达式?

// 匹配方法执行
execution(modifiers? return-type declaring-type? method-name(param-types) throws?)

// 示例
execution(public * *(..))                          // 所有public方法
execution(* set*(..))                              // 所有set开头的方法
execution(* com.example.service.*.*(..))           // service包下所有方法
execution(* com.example.service..*.*(..))          // service包及子包下所有方法
execution(* com.example.service.UserService.*(..)) // UserService的所有方法
execution(* com.example.service.UserService+.*(..)) // UserService及子类的所有方法
execution(* save*(String, ..))                     // 第一个参数是String的save开头方法

// 其他切入点
@within(com.example.anno.MyAnnotation)   // 标注了注解的类内的方法
@annotation(com.example.anno.MyAnnotation) // 标注了注解的方法
bean(userService)                        // 特定名称的Bean
args(String, ..)                         // 参数匹配

6.3 Spring事务

Q86: Spring事务的实现方式?

// 1. 声明式事务(推荐)
@Service
public class UserService {
    
    @Transactional
    public void saveUser(User user) {
        // 业务逻辑
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(Log log) {
        // 独立事务
    }
}

// 2. 编程式事务
@Service
public class UserService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void saveUser(User user) {
        transactionTemplate.execute(status -> {
            try {
                // 业务逻辑
            } catch (Exception e) {
                status.setRollbackOnly();
            }
            return null;
        });
    }
}

Q87: 事务传播行为?

传播行为 说明
REQUIRED(默认) 有事务就加入,没有就新建
REQUIRES_NEW 总是新建事务,挂起当前事务
SUPPORTS 有事务就加入,没有就非事务执行
NOT_SUPPORTED 非事务执行,挂起当前事务
MANDATORY 必须在事务中,否则抛异常
NEVER 必须非事务,否则抛异常
NESTED 嵌套事务(保存点)
// 场景:主事务失败回滚,子事务独立提交
@Transactional
public void main() {
    // 主逻辑
    sub();  // 即使这里成功,主事务失败也会回滚
    // 如果sub用REQUIRES_NEW,sub独立事务不会回滚
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sub() {
    // 子逻辑
}

Q88: 事务失效的场景?

@Service
public class UserService {
    
    // 1. 方法不是public
    @Transactional
    private void method1() {}  // 失效
    
    // 2. 同类方法自调用
    public void method2() {
        this.method3();  // 失效,未经过代理
    }
    
    @Transactional
    public void method3() {}
    
    // 3. 异常被捕获
    @Transactional
    public void method4() {
        try {
            // 业务逻辑
            throw new RuntimeException();
        } catch (Exception e) {
            // 异常被捕获,事务不回滚
        }
    }
    
    // 4. 抛出checked异常
    @Transactional
    public void method5() throws Exception {
        throw new Exception();  // 默认只回滚RuntimeException
    }
    
    // 解决:指定回滚异常
    @Transactional(rollbackFor = Exception.class)
    public void method6() throws Exception {
        throw new Exception();  // 会回滚
    }
    
    // 5. 数据库不支持事务
    // MySQL的MyISAM引擎不支持事务
}

6.4 Spring MVC

Q89: Spring MVC工作流程?

1. 用户请求 → DispatcherServlet
2. DispatcherServlet → HandlerMapping(获取Handler)
3. HandlerMapping → 返回HandlerExecutionChain(Handler + Interceptors)
4. DispatcherServlet → HandlerAdapter(执行Handler)
5. HandlerAdapter → Controller(业务处理)
6. Controller → 返回ModelAndView
7. DispatcherServlet → ViewResolver(解析视图)
8. ViewResolver → 返回View
9. DispatcherServlet → View(渲染视图)
10. View → 返回响应

Q90: Spring MVC常用注解?

@Controller                     // 控制器
@RestController                  // = @Controller + @ResponseBody
@RequestMapping("/api")          // 路径映射
@GetMapping                      // GET请求
@PostMapping                     // POST请求
@PutMapping                      // PUT请求
@DeleteMapping                   // DELETE请求

@RequestParam                    // 请求参数
@PathVariable                    // 路径变量
@RequestBody                     // 请求体
@RequestHeader                   // 请求头
@CookieValue                     // Cookie值

@ResponseBody                    // 响应体(JSON)
@ResponseStatus                  // 响应状态码

@ControllerAdvice                // 全局异常处理
@ExceptionHandler                // 异常处理方法

6.5 Spring Boot

Q91: Spring Boot自动装配原理?

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// @SpringBootApplication = @SpringBootConfiguration 
//                         + @EnableAutoConfiguration 
//                         + @ComponentScan

// @EnableAutoConfiguration核心
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

// AutoConfigurationImportSelector读取
// META-INF/spring.factories
// 加载EnableAutoConfiguration配置的类

// 示例:spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

// 条件装配
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
public class DataSourceAutoConfiguration {
    // 只有当classpath存在DataSource且容器中没有DataSource时才装配
}

Q92: Spring Boot常用注解?

// 配置类
@Configuration                  // 配置类
@Bean                           // Bean定义
@ConfigurationProperties        // 配置属性绑定
@Value("${app.name}")           // 属性注入

// 条件装配
@ConditionalOnClass             // 类路径存在指定类
@ConditionalOnMissingBean       // 容器中不存在指定Bean
@ConditionalOnProperty          // 属性满足条件
@ConditionalOnWebApplication    // Web应用

// 启动类
@SpringBootApplication          // 启动类注解
@EnableScheduling               // 开启定时任务
@EnableAsync                    // 开启异步
@EnableCaching                  // 开启缓存

第七部分:数据库

7.1 MySQL基础

Q93: MySQL存储引擎?

引擎 事务 锁 外键 适用场景
InnoDB 支持 行锁 支持 OLTP,高并发
MyISAM 不支持 表锁 不支持 读多写少
Memory 不支持 表锁 不支持 临时表
-- 查看存储引擎
SHOW ENGINES;

-- 指定表的存储引擎
CREATE TABLE t (id INT) ENGINE=InnoDB;

Q94: MySQL索引类型?

索引类型 说明
主键索引 唯一且非空,聚簇索引
唯一索引 唯一,可为空
普通索引 无限制
组合索引 多列组合
全文索引 文本搜索
空间索引 地理数据
-- 创建索引
CREATE INDEX idx_name ON t(name);
CREATE UNIQUE INDEX idx_email ON t(email);
CREATE INDEX idx_name_age ON t(name, age);  -- 组合索引

-- 最左前缀原则
-- idx_name_age(name, age)
SELECT * FROM t WHERE name = 'Tom';           -- 命中
SELECT * FROM t WHERE name = 'Tom' AND age = 20;  -- 命中
SELECT * FROM t WHERE age = 20;               -- 不命中
SELECT * FROM t WHERE name LIKE 'T%';         -- 命中
SELECT * FROM t WHERE name LIKE '%T';         -- 不命中

Q95: 索引的底层数据结构?

B+树(MySQL InnoDB使用)

特点:
1. 非叶子节点只存储键值和指针
2. 叶子节点存储所有数据,形成有序链表
3. 叶子节点通过指针连接,便于范围查询
4. 树的高度低(通常3层),减少磁盘I/O

优势:
1. 单节点存储更多key,树更矮
2. 叶子节点链表,范围查询高效
3. 磁盘I/O次数少

Q96: 聚簇索引和非聚簇索引?

特性 聚簇索引 非聚簇索引
数据存储 叶子节点存数据 叶子节点存主键值
数量 1个 多个
查询 直接获取 回表查询
存储 按主键顺序存储 独立存储
-- 回表查询
-- 非聚簇索引idx_name(name)
SELECT * FROM t WHERE name = 'Tom';
-- 1. 在idx_name中查找name='Tom'的主键id
-- 2. 在聚簇索引中查找id对应的数据(回表)

-- 覆盖索引(避免回表)
SELECT id, name FROM t WHERE name = 'Tom';  -- 只查索引列,无需回表

7.2 事务

Q97: 事务的ACID特性?

特性 说明
原子性(Atomicity) 全部成功或全部失败
一致性(Consistency) 数据状态一致
隔离性(Isolation) 事务间互不干扰
持久性(Durability) 提交后永久保存

Q98: 事务隔离级别?

隔离级别 脏读 不可重复读 幻读
读未提交(READ UNCOMMITTED) ✓ ✓ ✓
读已提交(READ COMMITTED) ✗ ✓ ✓
可重复读(REPEATABLE READ) ✗ ✗ ✓(MySQL InnoDB解决了)
串行化(SERIALIZABLE) ✗ ✗ ✗
-- MySQL默认隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 事务操作
START TRANSACTION;
COMMIT;
ROLLBACK;

Q99: MVCC是什么?

Multi-Version Concurrency Control(多版本并发控制):不加锁实现并发读

-- 核心概念
-- 1. 隐藏字段
--    - DB_TRX_ID:最后插入或更新的事务ID
--    - DB_ROLL_PTR:回滚指针,指向undo日志

-- 2. undo日志
--    记录数据的历史版本,用于回滚和读历史数据

-- 3. Read View
--    决定可见性,包含:
--    - m_ids:活跃事务ID列表
--    - min_trx_id:最小活跃事务ID
--    - max_trx_id:下一个事务ID
--    - creator_trx_id:创建该Read View的事务ID

-- 可见性判断
-- 如果 DB_TRX_ID < min_trx_id,可见
-- 如果 DB_TRX_ID >= max_trx_id,不可见
-- 如果 DB_TRX_ID 在 m_ids 中,不可见
-- 否则可见

7.3 SQL优化

Q100: SQL优化方法?

-- 1. 使用EXPLAIN分析执行计划
EXPLAIN SELECT * FROM t WHERE name = 'Tom';

-- 关键字段
-- id:执行顺序
-- type:访问类型(const > eq_ref > ref > range > index > ALL)
-- key:使用的索引
-- rows:预估扫描行数
-- Extra:额外信息(Using index = 覆盖索引)

-- 2. 避免SELECT *
SELECT id, name FROM t;  -- 好
SELECT * FROM t;         -- 差

-- 3. 避免索引失效
-- 函数操作
WHERE YEAR(create_time) = 2024;   -- 索引失效
WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';  -- 好

-- 隐式转换
WHERE phone = 13800138000;        -- phone是varchar,索引失效
WHERE phone = '13800138000';      -- 好

-- LIKE左模糊
WHERE name LIKE '%Tom';           -- 索引失效
WHERE name LIKE 'Tom%';           -- 好

-- OR连接
WHERE name = 'Tom' OR age = 20;   -- 可能索引失效
-- 改为UNION ALL

-- 4. LIMIT分页优化
SELECT * FROM t LIMIT 10000, 10;  -- 差,扫描前10010行
-- 优化:使用子查询
SELECT * FROM t WHERE id >= (
    SELECT id FROM t LIMIT 10000, 1
) LIMIT 10;

-- 5. 使用覆盖索引
SELECT id, name FROM t WHERE name = 'Tom';  -- 好

-- 6. 避免大事务
-- 拆分大事务为小事务

第八部分:Redis缓存

8.1 Redis基础

Q101: Redis数据类型?

类型 说明 使用场景
String 字符串、数字 缓存、计数器、分布式锁
Hash 哈希表 对象存储
List 列表 消息队列、列表
Set 无序集合 标签、交集并集
ZSet 有序集合 排行榜
Bitmap 位图 签到、统计
HyperLogLog 基数统计 UV统计
Geo 地理位置 位置相关
Stream 流 消息队列
# String
SET key value
GET key
INCR key
SETEX key 60 value  # 设置60秒过期

# Hash
HSET user:1 name Tom age 20
HGET user:1 name
HMGET user:1 name age

# List
LPUSH list a b c
RPOP list
LRANGE list 0 -1

# Set
SADD set a b c
SMEMBERS set
SINTER set1 set2

# ZSet
ZADD zset 1 a 2 b 3 c
ZRANGE zset 0 -1 WITHSCORES
ZREVRANGE zset 0 9  # 排行榜前10

Q102: Redis为什么快?

  1. 内存存储:数据在内存中,读写速度快
  2. 单线程:避免线程切换和锁竞争
  3. IO多路复用:epoll实现高并发
  4. 高效数据结构:SDS、跳表、压缩列表等

8.2 缓存问题

Q103: 缓存穿透?

问题:查询不存在的数据,缓存和数据库都无数据,请求直达数据库

解决方案:

// 1. 缓存空值
public Object get(String key) {
    Object value = redis.get(key);
    if (value != null) {
        return value;
    }
    
    Object dbValue = db.query(key);
    if (dbValue == null) {
        redis.setex(key, 60, "");  // 缓存空值,短过期时间
    } else {
        redis.set(key, dbValue);
    }
    return dbValue;
}

// 2. 布隆过滤器
public Object get(String key) {
    if (!bloomFilter.mightContain(key)) {
        return null;  // 一定不存在
    }
    // 可能存在,查询缓存和数据库
}

Q104: 缓存击穿?

问题:热点数据过期,大量请求直达数据库

解决方案:

// 1. 热点数据永不过期
redis.set(key, value);  // 不设置过期时间

// 2. 互斥锁
public Object get(String key) {
    Object value = redis.get(key);
    if (value != null) {
        return value;
    }
    
    String lockKey = "lock:" + key;
    try {
        // 尝试获取锁
        if (redis.setnx(lockKey, "1", 10)) {
            value = db.query(key);
            redis.set(key, value, 3600);
            return value;
        } else {
            // 等待重试
            Thread.sleep(50);
            return get(key);
        }
    } finally {
        redis.del(lockKey);
    }
}

Q105: 缓存雪崩?

问题:大量缓存同时过期,或Redis宕机

解决方案:

// 1. 过期时间随机
int expire = 3600 + new Random().nextInt(600);  // 1小时±10分钟
redis.set(key, value, expire);

// 2. 高可用架构
// 主从复制 + 哨兵 或 Redis Cluster

// 3. 熔断降级
@HystrixCommand(fallbackMethod = "getFallback")
public Object get(String key) {
    // 正常逻辑
}

public Object getFallback(String key) {
    // 降级逻辑:返回默认值或从本地缓存读取
}

8.3 Redis持久化

Q106: RDB和AOF的区别?

特性 RDB AOF
存储内容 内存快照 写命令日志
文件大小 小 大
恢复速度 快 慢
数据完整性 可能丢失 更完整
性能影响 小 大
# RDB配置
save 900 1      # 900秒内有1次写操作就保存
save 300 10     # 300秒内有10次写操作
save 60 10000   # 60秒内有10000次写操作

# AOF配置
appendonly yes
appendfsync everysec  # 每秒同步

# 混合持久化(推荐)
aof-use-rdb-preamble yes

第九部分:分布式与微服务

9.1 分布式锁

Q107: 如何实现分布式锁?

// Redis实现
public boolean tryLock(String key, String value, long expire) {
    // SET NX EX原子操作
    return redis.setnx(key, value, expire);
}

public boolean releaseLock(String key, String value) {
    // Lua脚本保证原子性
    String script = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "   return redis.call('del', KEYS[1]) " +
        "else " +
        "   return 0 " +
        "end";
    return redis.eval(script, key, value);
}

// Redisson(推荐)
RLock lock = redisson.getLock("myLock");
try {
    lock.lock();
    // 业务逻辑
} finally {
    lock.unlock();
}

// Zookeeper实现
InterProcessMutex lock = new InterProcessMutex(client, "/locks/myLock");
try {
    lock.acquire();
    // 业务逻辑
} finally {
    lock.release();
}

9.2 消息队列

Q108: 消息队列的使用场景?

场景 说明
异步处理 发送邮件、短信等异步执行
应用解耦 生产者和消费者独立部署
流量削峰 高峰时消息堆积,平滑处理
日志处理 日志收集和分析

Q109: 如何保证消息不丢失?

生产者端:
1. 发送确认(ACK)
2. 本地消息表 + 定时任务

Broker端:
1. 同步刷盘
2. 多副本同步复制

消费者端:
1. 手动确认(ACK)
2. 幂等处理

Q110: 如何保证消息顺序?

// 方案1:单队列单消费者
// 缺点:性能差

// 方案2:分区队列
// 同一业务ID的消息发到同一分区
int partition = orderId.hashCode() % partitionCount;
producer.send(topic, partition, key, message);

// 消费者单线程处理同一分区的消息

9.3 微服务

Q111: 微服务架构组件?

组件 作用 实现
注册中心 服务注册与发现 Nacos、Eureka、Consul
配置中心 统一配置管理 Nacos、Apollo
网关 统一入口、路由、限流 Spring Cloud Gateway
负载均衡 请求分发 Ribbon、LoadBalancer
服务调用 服务间通信 Feign、Dubbo
熔断降级 故障保护 Sentinel、Hystrix
链路追踪 调用链追踪 Skywalking、Zipkin

Q112: CAP理论?

特性 说明
Consistency(一致性) 所有节点数据一致
Availability(可用性) 每个请求都有响应
Partition Tolerance(分区容错) 网络分区时系统继续运行

选择:

  • CA:单机数据库(MySQL单机)
  • CP:分布式数据库(HBase、MongoDB)
  • AP:注册中心(Eureka、Nacos AP模式)

第十部分:设计模式

10.1 创建型模式

Q113: 单例模式?

// 饿汉式(线程安全)
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

// 双重检查锁
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;
    }
}

// 静态内部类(推荐)
public class Singleton {
    private Singleton() {}
    
    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

// 枚举(最佳实践)
public enum Singleton {
    INSTANCE;
}

Q114: 工厂模式?

// 简单工厂
public class ShapeFactory {
    public static Shape createShape(String type) {
        switch (type) {
            case "circle": return new Circle();
            case "square": return new Square();
            default: throw new IllegalArgumentException();
        }
    }
}

// 工厂方法
public interface ShapeFactory {
    Shape createShape();
}

public class CircleFactory implements ShapeFactory {
    @Override
    public Shape createShape() {
        return new Circle();
    }
}

// 抽象工厂
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

public class WindowsFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }
    
    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

10.2 结构型模式

Q115: 代理模式?

// 静态代理
public interface Subject {
    void request();
}

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject");
    }
}

public class Proxy implements Subject {
    private RealSubject realSubject;
    
    @Override
    public void request() {
        before();
        realSubject.request();
        after();
    }
    
    private void before() { System.out.println("Before"); }
    private void after() { System.out.println("After"); }
}

// JDK动态代理
public class JdkProxy implements InvocationHandler {
    private Object target;
    
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }
    
    private void before() {}
    private void after() {}
}

// CGLIB动态代理
public class CglibProxy implements MethodInterceptor {
    public Object getProxy(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        Object result = proxy.invokeSuper(obj, args);
        after();
        return result;
    }
}

10.3 行为型模式

Q116: 策略模式?

// 定义策略接口
public interface PaymentStrategy {
    void pay(int amount);
}

// 具体策略
public class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("支付宝支付:" + amount);
    }
}

public class WechatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("微信支付:" + amount);
    }
}

// 上下文
public class PaymentContext {
    private PaymentStrategy strategy;
    
    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void pay(int amount) {
        strategy.pay(amount);
    }
}

// 使用
PaymentContext context = new PaymentContext();
context.setStrategy(new AlipayStrategy());
context.pay(100);

Q117: 观察者模式?

// 定义观察者接口
public interface Observer {
    void update(String message);
}

// 具体观察者
public class UserObserver implements Observer {
    private String name;
    
    public UserObserver(String name) {
        this.name = name;
    }
    
    @Override
    public void update(String message) {
        System.out.println(name + "收到消息:" + message);
    }
}

// 被观察者
public class Subject {
    private List<Observer> observers = new ArrayList<>();
    
    public void attach(Observer observer) {
        observers.add(observer);
    }
    
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    
    public void notifyAll(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

// Java内置观察者模式
// Observable(被观察者) + Observer(观察者)
// 或使用事件监听机制

第十一部分:算法与数据结构

11.1 常见算法

Q118: 排序算法比较?

算法 平均时间 最坏时间 空间 稳定性
冒泡排序 O(n²) O(n²) O(1) 稳定
选择排序 O(n²) O(n²) O(1) 不稳定
插入排序 O(n²) O(n²) O(1) 稳定
快速排序 O(nlogn) O(n²) O(logn) 不稳定
归并排序 O(nlogn) O(nlogn) O(n) 稳定
堆排序 O(nlogn) O(nlogn) O(1) 不稳定
计数排序 O(n+k) O(n+k) O(k) 稳定

Q119: 快速排序实现?

public void quickSort(int[] arr, int left, int right) {
    if (left < right) {
        int pivot = partition(arr, left, right);
        quickSort(arr, left, pivot - 1);
        quickSort(arr, pivot + 1, right);
    }
}

private int partition(int[] arr, int left, int right) {
    int pivot = arr[right];
    int i = left - 1;
    for (int j = left; j < right; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(arr, i, j);
        }
    }
    swap(arr, i + 1, right);
    return i + 1;
}

private void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

11.2 数据结构

Q120: 常见数据结构?

数据结构 特点 时间复杂度
数组 连续内存,随机访问 访问O(1),插入删除O(n)
链表 非连续内存 访问O(n),插入删除O(1)
栈 LIFO 入栈出栈O(1)
队列 FIFO 入队出队O(1)
哈希表 键值对 平均O(1)
树 层次结构 查找O(logn)
图 网络关系 取决于算法

结语

本文档涵盖了Java面试的核心知识点,从基础语法到高级架构,帮助系统化复习。

建议:

  1. 理解原理,不要死记硬背
  2. 结合代码实践
  3. 关注新技术发展
  4. 多做项目积累经验

祝你面试顺利!🎉

动物装饰