一、Java 基础语法 & 关键字
1. JDK、JRE 和 JVM 的区别
- JVM(Java Virtual Machine):Java 虚拟机,负责执行字节码(
.class
)。提供类加载、字节码校验、内存管理(堆、方法区)、执行引擎、垃圾回收、线程调度等。是抽象的规范,具体由不同厂商实现(HotSpot、OpenJ9 等)。 - JRE(Java Runtime Environment):运行环境,包含 JVM、核心类库(rt.jar 或 modules)、以及运行时需要的其它资源。用于运行 Java 程序。
- JDK(Java Development Kit):开发工具包,包含 JRE + 开发工具(
javac
、jar
、javadoc
、调试工具等)。用于开发 Java 程序。
总结:JDK ⊃ JRE ⊃ JVM(JDK 包含 JRE,JRE 包含 JVM)。
2. Java 的跨平台原理是什么?
- Java 程序先由
javac
编译成与平台无关的 字节码(.class
)。字节码不是机器码,而是供 JVM 执行的指令集。 - 每个平台(Windows/Linux/macOS)有相应的 JVM 实现,JVM 把字节码翻译成该平台的机器指令(通过解释器、JIT 编译器等)。因此同一套
.class
可以在不同平台的 JVM 上运行 —— “一次编写,处处运行”(Write Once, Run Anywhere)。 - 其它保证跨平台的要点:标准库隐藏平台差异(I/O、线程等),需避免使用与平台绑定的本地代码(JNI)。
3. Java 的基本数据类型有哪些?各自的默认值?
8 种基本类型及默认值(成员变量/数组元素;局部变量无默认值必须初始化):
byte
:1 byte,默认值0
short
:2 bytes,默认值0
int
:4 bytes,默认值0
long
:8 bytes,默认值0L
float
:4 bytes,默认值0.0f
double
:8 bytes,默认值0.0d
char
:2 bytes(UTF-16 code unit),默认值'\u0000'
(即 0)boolean
:JVM 语义上用 1 bit 表示,默认值false
另外,引用类型(对象、数组等)的默认值是 null
。
4. byte
、short
、int
、long
各占多少字节?
byte
:1 字节(8 位)short
:2 字节(16 位)int
:4 字节(32 位)long
:8 字节(64 位)
(上述为 Java 语言规范定义,与平台无关。)
5. float
和 double
的区别?
- 精度与存储:
float
:32 位单精度浮点(IEEE 754),约 6~7 位有效数字。double
:64 位双精度浮点(IEEE 754),约 15~16 位有效数字。
- 精度更高的
double
更常用;float
用于节省内存或与特定 API(如图形库)兼容。 - 注意:浮点数有精度误差,不适合做精确货币计算(应使用
BigDecimal
)。 - 默认浮点字面量为
double
(例如1.2
是double
,写成1.2f
才是float
)。
6. char
类型占几个字节?能存汉字吗?
char
占 2 字节(16 位),表示 UTF-16 的一个 code unit。- 能否存汉字:多数常用汉字(位于基本多文种平面 BMP)可以用单个
char
表示(例如'你'
);但有些罕见汉字或 emoji 属于补充平面(Supplementary Planes),它们需要 两个 char(称为 surrogate pair) 来表示。 - 因此,单个
char
能表示一个 UTF-16 code unit,而不是完整的 Unicode code point。处理 Unicode 字符时注意使用int codePoint
/Character
的相关 API。
7. 自动装箱(autoboxing)和拆箱(unboxing)的原理是什么?
概念:
- 装箱:把基本类型自动转换成对应的包装类型(如
int
→Integer
)。 - 拆箱:把包装类型自动转换回基本类型(如
Integer
→int
)。
- 装箱:把基本类型自动转换成对应的包装类型(如
编译器行为:自动装箱/拆箱是在编译期由 Java 编译器插入对应的调用,示例:
1
2Integer a = 10; // 编译器会转换为 Integer.valueOf(10)
int b = a; // 编译器会转换为 a.intValue()valueOf 缓存:包装类(如
Integer.valueOf(int)
)会对小整数(默认 -128 到 127)做缓存以重用对象,减少频繁创建对象。注意事项:
拆箱
null
会抛出NullPointerException
:1
2Integer x = null;
int y = x; // NPE装箱/拆箱会有性能开销(对象创建、装箱/拆箱方法调用),在性能敏感场景尽量使用基本类型或手动优化。
比较时注意:
Integer a = 100; Integer b = 100; a==b
在缓存范围内可能为true
,超出范围则通常为false
。
8. ==
和 equals()
的区别?
- ==:
- 对于基本类型:比较 值(数值是否相等)。
- 对于引用类型:比较 引用地址(是否是同一个对象)。
- equals():
- 是
Object
的方法,默认实现也是比较引用(等价于==
)。 - 许多类(如
String
、Integer
、集合类等)重写了equals()
,用于比较逻辑/内容相等。
- 是
- 使用建议:
- 比较对象内容用
equals()
(需检查null
),比较是否同一实例用==
。 - 与
equals()
配套应重写hashCode()
(见下题)。
- 比较对象内容用
9. hashCode
和 equals
的关系?
- 合同(Contract)(重要):
- 如果两个对象通过
equals()
被判定为相等(a.equals(b)
为true
),那么a.hashCode() == b.hashCode()
必须成立。 - 反之不要求:
hashCode
相等的对象不一定equals()
相等(哈希冲突允许)。
- 如果两个对象通过
- 在哈希集合中的角色:
- 哈希表(
HashMap
/HashSet
)先用hashCode()
找到桶(bucket),若桶中有多个元素,再用equals()
逐个比较确认相等或冲突。 - 如果只重写
equals()
而不重写hashCode()
会破坏集合行为(例如放入HashSet
后无法正确查找)。
- 哈希表(
- 实现要点:
hashCode()
要尽量分散(降低冲突),并在对象不可变字段上基于相同规则计算。- 若对象可变,若用于哈希集合要小心:修改字段会导致
hashCode()
改变,破坏集合内部结构。
10. String
、StringBuffer
、StringBuilder
的区别?
- String:
- 不可变(immutable),每次修改都会产生新的对象(或新内部 char/byte 数组)。
- 线程安全(因为不可变),适合频繁读取、少量修改的场景。
- StringBuffer:
- 可变的字符序列(内部有缓冲区
char[]
/byte[]
),几乎与StringBuilder
接口相同。 - 线程安全,其方法大多使用
synchronized
,因此在多线程下可以被多个线程安全使用。 - 相对较慢(同步开销)。
- 可变的字符序列(内部有缓冲区
- StringBuilder(Java 5+):
- 可变,非线程安全(没有同步),比
StringBuffer
快。 - 推荐在单线程或外部已同步的场景下使用。
- 可变,非线程安全(没有同步),比
- 选择建议:
- 多线程需要可变字符串:
StringBuffer
(或外部同步)。 - 单线程/局部构造字符串:
StringBuilder
。 - 常量字符串或少量拼接:
String
(编译器对+
会优化为StringBuilder
)。
- 多线程需要可变字符串:
11. 为什么 String
是不可变的(immutable)?
原因(几条重要理由):
- 安全性:
String
经常用于关键场景(类加载器、网络地址、文件名、权限检查、数据库连接字符串等)。不可变保证在传递引用时不被恶意或意外修改。 - 线程安全:不可变对象固有线程安全,多个线程可共享同一
String
实例而无需同步。 - 性能(缓存 hashCode):
String
的哈希值可缓存(hash
字段),便于作为 Map 的 key,避免重复计算。 - 字符串常量池:可安全地将字面量放入池中重用,不用复制或担心修改。
- 优化:JVM 可进行共享、常量折叠等优化(更易于实现某些编译期/运行期优化)。
12. String
常量池的实现机制?
- 概念:编译期和运行期维护一个字符串池(String Intern Pool),用于存放字符串字面量(literal)和显式
intern()
后的字符串,以便重用相同内容的String
实例。 - 编译期:源代码中的字符串字面量(如
"abc"
)会被放到常量池(编译后的 class 文件常量池),类加载后这些字面量会放入运行时的字符串池。 - 运行期:
- 以前(Java 6 及更早):字符串常量池在 PermGen(方法区)中。
- Java 7 起:常量池迁移到 Java 堆(运行时常量池也在堆里),避免 PermGen 问题。
- intern():当调用
s.intern()
时,JVM 会检查池中是否已有相同内容的字符串:- 若存在,返回池中的引用;
- 若不存在,将该字符串的引用加入池并返回它。
- 编译时优化:字符串常量的拼接(编译时常量)会在编译期合并,如
"a" + "b"
→"ab"
,直接放入常量池;而运行时拼接(变量参与)则使用StringBuilder
。
13. new String("abc")
创建了几个对象?
- 通常情形:
- 若
"abc"
字面量尚未在常量池中存在,执行new String("abc")
会导致 两个对象 被创建:常量池中的"abc"
(一个String
)和堆中通过new
创建的新的String
实例(内容通常是对常量池中字符数组的复制或共享,具体实现随 Java 版本而异)。 - 如果字面量
"abc"
已经存在于常量池中(例如之前被加载过),那么new String("abc")
只会创建 一个堆对象(new 的那个String
)。
- 若
- 注意:自 Java 7/9 后
String
内部实现改变(压缩字符串、byte[] 存储等),但逻辑上上述结论成立:通常 1 或 2 个对象,取决于常量池中是否已存在该字面量。
14. final
关键字的作用?
- 用于类(final class):类不可被继承(如
String
)。 - 用于方法(final 方法):方法不能被子类重写(override),用于确保行为不可变。
- 用于变量(final 字段/局部变量):
- 对基本类型:赋值后值不可改变(常量)。
- 对引用类型:引用不可改变(不能指向另一个对象),但所指向对象的内部状态仍可变(除非对象本身不可变)。
static final
常用来定义常量(编译期常量)。
- 用于参数(方法形参可以声明为
final
):代表方法内不能修改该参数引用/值。 - 其它用途:在多线程中,
final
字段的写-构造过程被 JMM(Java 内存模型)处理,确保构造完成后其他线程可见(安全发布方面有好处)。
15. static
关键字的作用?
- 用于声明类级别成员(字段、方法、初始化块、嵌套类),不依赖实例。
- static 变量:类变量,所有实例共享一份。内存中仅有一份副本。
- static 方法:类方法,可通过
ClassName.method()
调用;不能直接访问非静态成员(因无 this)。 - static 代码块:类加载时执行一次(用于静态初始化)。
- static 嵌套类:可以声明为静态的内部类(静态嵌套类),没有外部实例引用。
16. static
修饰变量、方法、代码块分别意味着什么?
- static 变量(类变量):
- 随类加载而创建,所有对象共享同一份数据。
- 可通过
ClassName.field
或instance.field
(不推荐)访问。
- static 方法:
- 属于类,调用时无需实例。
- 不能使用
this
、不能直接访问非静态成员。 - 可作为工具方法(如
Math.abs()
)。
- static 代码块:
- 在类加载阶段执行一次,用于复杂静态初始化(比如初始化静态常量、加载本地库等)。
- 执行顺序:静态块按定义顺序执行,类加载时运行(在实例化之前)。
17. static
和 final
能一起用吗?
能。
static final
常用于定义类常量(尤其是基本类型和String
),例如:1
2public static final int MAX = 100;
public static final String NAME = "abc";区别:
- 如果是编译期常量(
static final
基本类型或String
且在编译时可以确定),编译器会将其内联到使用处(被引用的类编译后看到的是常量值),注意跨模块修改可能导致需要重新编译引用方。 - 如果是
static final
引用对象且不是编译期常量,则引用本身不可变,但对象内容可能可变。
- 如果是编译期常量(
18. static
内部类和非静态内部类的区别?
- 静态内部类(static nested class):
- 相当于外部类的一个静态成员。
- 没有对外部类实例的隐式引用(不能直接访问外部类的非静态成员)。
- 可以像普通类那样实例化:
Outer.StaticInner inner = new Outer.StaticInner();
- 非静态内部类(inner class):
- 每个实例隐式持有一个外部类实例引用(
Outer.this
),可以直接访问外部类的所有成员(包括私有成员)。 - 创建方式:
Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();
- 占用外部类对象的内存引用,可能导致内存泄露(如果长期持有内部类实例导致外部类不能被回收)。
- 每个实例隐式持有一个外部类实例引用(
- 选择原则:如果内部类不需要访问外部实例成员,优先使用
static
嵌套类,避免额外引用。
19. Java 中的 this
和 super
的区别?
- this:
- 引用当前对象的引用。
- 用于访问当前类的实例变量、调用当前类的其他构造器(
this(...)
)、或传递当前对象引用。
- super:
- 引用当前对象的父类部分。
- 用于访问父类被覆盖的方法(
super.method()
)、父类字段(若有同名字段)、以及在子类构造器第一行调用父类构造器(super(...)
)。
- 注意:
this()
或super()
必须在构造器的第一行(两者不能同时出现)。super
不能用于访问父类的 private 成员(编译期限制)。
20. Java 中构造方法能否被重写?
- 不能。重写(override)适用于实例方法,构造方法不是继承的成员,子类不能重写父类的构造器。
- 可以重载(overload):在同一类中同名构造器有不同参数列表属于重载。
- 子类构造器可通过
super(...)
显式调用父类构造器来完成父类初始化。
21. 接口和抽象类的区别?
- 接口(interface):
- 早期(Java 7 之前):只包含抽象方法(默认是
public abstract
)和public static final
常量。 - Java 8+:可以包含
default
方法与static
方法(可以有部分实现);Java 9+ 允许private
方法。 - 支持多继承(一个类可以实现多个接口)。
- 没有实例字段(除
static final
常量外)。
- 早期(Java 7 之前):只包含抽象方法(默认是
- 抽象类(abstract class):
- 可以有实例字段(非静态成员变量)、已有实现的方法(具体方法)、构造器。
- 适合表示一类有共同行为并共享状态的类层次。
- 类只能单继承(只能继承一个抽象类)。
- 选择建议:
- 如果需要多继承行为、只定义方法契约且不需要状态,使用接口。
- 如果需要提供共有实现和状态,使用抽象类。
22. 接口中可以有 default
方法和 static
方法吗?
- 可以(Java 8 起)。
default
方法:为实现类提供默认实现,避免破坏已有实现(接口演化)。实现类可以覆盖(override)。static
方法:属于接口自身,可以通过InterfaceName.method()
调用,不能通过实例调用。
- Java 9 及以后还可有
private
方法,供接口内部复用实现逻辑。
23. Java 8 接口新增了什么?
- Java 8 在接口中新增了 default 方法 和 static 方法,允许接口提供带实现的方法,从而便于接口的演化(向后兼容)。这也是 Java 8 引入函数式编程支持的基础之一(允许接口作为函数式接口)。
24. Java 8 有什么新特性,并详细介绍(重点)
一、Java 8 新特性深入解析
好的,我来帮你把 Java 8 的六大核心特性(Lambda、函数式接口、Stream API、方法引用、java.time API、Optional) 进行 完整整合,从概念、示例、底层原理、实现机制、特点和优势做一个统一、连贯的解析。
Java 8 六大核心特性整合解析
1. Lambda 表达式
概念
- 匿名函数,允许把行为作为参数传递。
- 简化匿名内部类写法,实现函数式编程。
示例
1 | List<String> list = Arrays.asList("a", "bb", "ccc"); |
底层原理
- 编译器生成静态/实例方法 +
invokedynamic
指令。 - JVM 调用
LambdaMetafactory
动态生成函数对象,实现 函数式接口。 - 捕获外部变量通过闭包机制存储(必须是
effectively final
)。
特点
- 语法简洁
- 可作为参数传递行为
- 性能高于匿名内部类(减少对象创建)
2. 函数式接口
概念
- 仅有一个抽象方法的接口,用作 Lambda 表达式的目标类型。
- 可用
@FunctionalInterface
标识。
示例
1 | @FunctionalInterface |
底层原理
- Lambda 表达式编译时生成实现函数式接口的函数对象。
- 捕获变量通过闭包对象保存。
特点
- 简化匿名类
- 支持函数式编程
- 可与 Stream / Optional 等结合
3. Stream API
概念
- 对集合进行声明式操作(过滤、映射、归约)。
- 支持 惰性求值 和 并行处理。
示例
1 | List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); |
底层原理
- Stream 是流水线对象,中间操作返回新的 Stream,终端操作触发计算。
- 串行流:顺序迭代器处理
- 并行流:ForkJoinPool 分块处理
特点
- 声明式、链式操作
- 支持并行
- 可与 Lambda / 方法引用结合
4. 方法引用
概念
- Lambda 表达式的简化写法,引用现有方法。
- 类型:
- 静态方法引用:
ClassName::staticMethod
- 实例方法引用:
instance::method
- 构造器引用:
ClassName::new
- 静态方法引用:
示例
1 | List<String> names = Arrays.asList("a", "bb", "ccc"); |
底层原理
- 编译器转成 Lambda 表达式 + invokedynamic。
- JVM 生成实现函数式接口的函数对象,内部持有方法引用。
特点
- 简洁直观
- 可减少 Lambda 代码
- 与 Stream / Optional 配合使用
5. java.time API
概念
- 替代
Date
/Calendar
,不可变、线程安全。 - 核心类:
LocalDate
/LocalTime
/LocalDateTime
(无时区)ZonedDateTime
(带时区)Duration
/Period
(时间段)
示例
1 | LocalDate today = LocalDate.now(); |
底层原理
- 内部字段
final
保存值,不可变。 - 工厂方法创建对象,链式操作返回新对象。
- 使用 enum + 整型/字节优化存储(如 LocalDate 年月日用 int)。
特点
- 不可变,线程安全
- 链式操作,易组合
- 支持各种历法和时区计算
6. Optional
概念
- 容器对象,防止
NullPointerException
。 - 可以包含值或为空(empty)。
示例
1 | Optional<String> opt1 = Optional.of("Hello"); |
底层原理
- 内部
value
字段存储非空值,空值使用单例EMPTY
。 map
/flatMap
/filter
返回新的 Optional,对象不可变。- 与 Lambda / 函数式接口结合实现链式安全操作。
特点
- 避免显式 null 判断
- 支持链式调用
- 可与 Stream / Lambda 配合
Java 8 六大核心特性底层对比表
特性 | 底层实现 | 核心原理 | 优势 |
---|---|---|---|
Lambda | invokedynamic + LambdaMetafactory | 动态生成函数对象 | 简洁、性能高 |
函数式接口 | 单抽象方法接口 | 捕获变量通过闭包存储 | 简化匿名类,函数式编程 |
Stream API | 流水线 + 延迟求值 | 中间操作惰性,终端触发 | 声明式、可并行 |
方法引用 | Lambda 简化语法 + invokedynamic | 内部持有方法引用 | 简洁直观,减少代码 |
java.time | 不可变对象 + 工厂方法 | final字段保存值,链式返回新对象 | 线程安全,可组合 |
Optional | 包装对象 + 单例 EMPTY | 不可变容器,函数式链式调用 | 避免 NPE,安全链式操作 |
1. Lambda 表达式
语法
1 | (parameters) -> expression |
示例:排序
1 | List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); |
图示
1 | 编译器把 Lambda 转成匿名类或 invokedynamic 机制 |
- 捕获外部变量:必须是
effectively final
,底层通过匿名类的构造器保存引用或值。
2. Stream API
特点
- 声明式:关注“做什么”,而非“怎么做”。
- 惰性求值:中间操作不会立即执行,终端操作触发。
- 可并行:使用
.parallelStream()
自动利用多核 CPU。
示例:数据处理
1 | List<String> words = Arrays.asList("Java","Python","C++","Go"); |
图示
1 | List -> Stream -> filter -> map -> sorted -> collect |
3. 方法引用
类型
- 静态方法:
Class::staticMethod
- 实例方法:
instance::method
- 构造器:
Class::new
示例
1 | List<String> names = Arrays.asList("a", "bb", "ccc"); |
4. java.time API
示例
1 | LocalDate date = LocalDate.of(2025, 9, 10); |
图示
1 | LocalDate/Time (不可变) |
- 线程安全:无需同步即可共享对象。
5. Optional
示例
1 | Optional<String> opt = Optional.ofNullable(null); |
图示
1 | Optional.ofNullable(value) |
二、synchronized 底层原理深入解析
1. JVM 锁的类型
锁类型 | 特点 | 性能 |
---|---|---|
偏向锁 (Biased Lock) | 无竞争时偏向第一个线程,轻量 | 高 |
轻量级锁 (Lightweight) | 竞争少时使用 CAS 升级锁 | 中 |
重量级锁 (Monitor) | 高竞争时使用 OS mutex,阻塞线程 | 低 |
2. synchronized 对象头结构
- 每个对象在 JVM 内存中都有 对象头(Mark Word),存储锁信息、哈希码、GC 标记等。
1 | 对象头 Mark Word (32/64bit) |
- 锁标记位:
- 00:无锁
- 01:偏向锁
- 10:轻量级锁
- 11:重量级锁
3. synchronized 执行流程
a) 偏向锁(无竞争)
1 | 线程获取锁 -> 在对象头打上线程ID -> 执行 -> 释放锁 -> 保持偏向状态 |
b) 轻量级锁(CAS竞争)
1 | 线程尝试CAS抢锁 -> 成功:持有锁 -> 执行 -> 释放 |
c) 重量级锁(阻塞)
1 | 线程无法获取锁 -> 阻塞(OS等待队列) -> 被唤醒 -> 获取锁 -> 执行 -> 释放锁 |
图示
1 | +------------------+ |
4. synchronized 方法示例
1 | class Counter { |
- 锁对象:实例方法锁住
this
,静态方法锁住类对象ClassName.class
。 - 效果:同一时间只有一个线程能进入
increment()
,保证count++
操作的原子性。
5. JVM 优化
- 锁消除:编译器或 JIT 可检测无竞争情况,消除同步锁。
- 锁粗化:把多次连续的小范围锁合并,减少加锁次数。
- 偏向锁 / 轻量级锁 / 重量级锁升级:动态适应竞争情况,兼顾性能与正确性。
如果你愿意,我可以帮你把 Java 8 新特性 和 synchronized 底层原理 做一张 图解全流程总结图,把 Lambda、Stream、锁状态、对象头等内容融合成一张直观的图,方便记忆和面试答题。
你希望我做这张图吗?25. 多态的实现机制是什么?
- 概念:同一操作作用于不同对象时表现出不同的行为(主要表现为方法的动态绑定)。
- 实现机制:
- 编译时:根据引用类型检查方法签名(静态类型检查)。
- 运行时:实际调用的方法根据对象的实际类型(运行时类型)决定;即 动态绑定(dynamic dispatch)。
- JVM 通常通过 虚方法表(vtable)/方法查找机制 实现。调用一个非
static
、非private
、非final
的方法时,会在运行时查找实际类的实现并调用。
- 结果:子类可以覆盖父类方法,调用者使用父类引用指向子类对象时,调用的是子类的覆盖方法(运行时决定)。
- 注意:
static
、private
、final
方法不会被动态绑定(是静态绑定)。
26. 方法重载(overload)和方法重写(override)的区别?
方法重载(Overloading):
- 同一类中方法名相同、参数列表不同(参数类型/个数/顺序),返回类型可以不同。
- 编译时决定(重载解析在编译期完成),与继承关系无关。
方法重写(Overriding):
- 子类定义与父类相同方法签名(方法名 + 参数类型相同)的实现,目的是改变/扩展父类行为。
- 运行时动态绑定,必须满足访问权限不能更严格,抛出的受检异常不能超过父类版本等规则。
示例:
1
2
3
4
5
6// overload
void f(int x) {}
void f(String s) {}
// override
class A { void m() {} }
class B extends A { @Override void m() {} }
27. Java 支持多继承吗?如何实现类似效果?
- 类的多继承:Java 不支持类的多继承(不能继承多个类),以避免菱形继承问题(diamond problem)。
- 实现类似效果的方法:
- 接口多实现:一个类可以实现多个接口(Java 8 的
default
方法也带来类似多继承方法实现的可能,但有冲突解决规则)。 - 组合/委托(composition/delegation):在类中持有其它类的实例并把调用委托给它们(优于继承的面向对象设计原则)。
- 接口多实现:一个类可以实现多个接口(Java 8 的
- 接口冲突解决:若多个接口提供相同默认方法,类必须重写该方法并明确调用哪个接口的默认实现(
InterfaceName.super.method()
)。
28. Java 的四种访问修饰符?
public
:对所有类可见(任何包)。protected
:对同包类和子类可见(即同包或子类可以访问)。- 默认(包私有,package-private)(不写修饰符):对同包类可见,包外不可见。
private
:仅在本类内可见,包外/子类不可访问(子类无法直接访问父类 private 成员)。- 注意:对类(顶层类)只能使用
public
或默认(包私有),不能声明为private
/protected
。
29. transient
关键字的作用?
- 用于标记字段在 Java 序列化(
Serializable
)过程中不被序列化。 - 被标记为
transient
的字段在序列化后不会写入序列化流,反序列化时这些字段会以默认值恢复(基本类型 0,引用类型null
)。 - 常用于敏感信息(密码)、不需要持久化的缓存字段、或可重建的状态字段。
- 注意:
static
字段本身也不会被序列化(因为是类级别,不属于实例状态)。
30. volatile
关键字的作用?
- 可见性:保证对
volatile
变量的写入对其它线程立即可见(读取总是从主内存而不是线程缓存读取)。 - 禁止指令重排序(部分):读/写
volatile
存在的内存屏障能保证一定的有序性(写volatile
发生在后续读的可见性上,更多细节参考 JMM)。 - 不保证原子性:对单次读/写操作是原子的(对
long
/double
在 Java 5 后也保证原子),但非原子操作(如i++
)不是原子的。 - 典型用途:
- 状态标志(如
volatile boolean running
)。 - 用于实现双重检查锁定(DCL)单例中的
instance
(在 Java 5+ 环境下有效)。
- 状态标志(如
- 与 synchronized 比较:
volatile
轻量,只保证可见性与部分有序性,不保证互斥;适用于简单状态通信。synchronized
提供互斥和可见性保证(更重),并可配合wait/notify
。
31. synchronized
的作用及底层原理?
作用
- 互斥(排它):保证同一时间只有一个线程可以执行被
synchronized
修饰的代码块或方法(针对同一把锁)。 - 可见性:进入/退出同步块会建立 happens-before 关系,确保锁释放前的写对随后获取该锁的线程可见。
- 用途:保护临界区、确保多个线程对共享可变状态的安全访问。
使用方式
- 方法级别:
synchronized void m() { ... }
:等价于synchronized(this)
(实例方法)或synchronized (ClassName.class)
(静态方法)。
- 代码块级别:
synchronized(this) { ... }
或synchronized(lockObj) { ... }
更灵活,能减小锁的粒度。
底层原理(HotSpot 实现概要)
- JVM 使用对象头(object header)中的 mark word 和关联数据结构维护锁状态。
- 锁优化策略(为提升性能,HotSpot 引入若干优化):
- 偏向锁(Biased Locking):在没有竞争的情况下,锁会偏向于第一次获得它的线程,重复获取无需 CAS,减少开销。
- 轻量级锁(Lightweight Locking):使用 CAS 操作在栈上记录加锁记录,避免进入重量级监视器(monitor)。
- 重量级锁(Monitor/Mutex):当竞争激烈或 CAS 失败时,升级为重量级锁,使用操作系统互斥量(可能涉及线程阻塞/唤醒)。
monitorenter
/monitorexit
是字节码指令(由编译器/字节码生成器生成)。- JIT 编译器可进行锁消除、锁粗化、锁优化等(当能证明无并发访问或已外部同步时)。
wait/notify
/notifyAll
Object.wait()
、notify()
、notifyAll()
必须在持有对象监视器(即在synchronized
块内)时调用,用于线程间协作(条件等待/通知)。wait()
会释放锁并进入等待队列;notify()
唤醒等待队列中的一个线程(被唤醒线程在重新获得锁后继续)。
注意与陷阱
- 锁粒度:避免用过大锁(如
synchronized
在方法头部锁住大量操作),谨慎使用String
或装箱对象作为锁(可能会导致多个实例共用同一锁或锁被外部持有)。 - 死锁:多线程锁顺序不当可能死锁;设计时谨防。
- 性能:在高并发下可考虑使用
java.util.concurrent
包(ReentrantLock
、ConcurrentHashMap
、AtomicXxx
)等更细粒度、高性能的并发工具。
二、面向对象编程(OOP)
- 面向对象的三大特性是什么?
- 封装的作用是什么?
- 多态的优点是什么?
- 重写方法时返回值能否不同?
- 构造函数能否被继承?
- 构造函数能否 private?
- 单例模式的几种实现方式?
- 饿汉式和懒汉式单例的区别?
- 为什么要使用内部类?
- 成员内部类、局部内部类、静态内部类的区别?
- Java 中对象的创建方式有哪些?
- 对象之间的浅拷贝和深拷贝的区别?
- clone() 方法的原理?
- 为什么 Java 不支持多继承?
- Java 是值传递还是引用传递?
- 方法参数传递时是如何处理对象的?
- 封装性体现在哪些方面?
- 为什么需要继承?
- 重写 Object 类的 toString 方法的意义?
- 重写 Object 类的 equals 方法时需要注意什么?
三、异常处理
- Java 的异常体系结构?
- Checked Exception 和 Unchecked Exception 的区别?
- throw 和 throws 的区别?
- try-catch-finally 的执行顺序?
- finally 中的 return 会覆盖 try 中的 return 吗?
- try-with-resources 的作用?
- 自定义异常类如何实现?
- Error 和 Exception 的区别?
- NullPointerException 常见场景有哪些?
- 运行时异常需要捕获吗?为什么?
四、集合框架(Collections)
- Collection 和 Collections 的区别?
- List、Set、Map 的区别?
- ArrayList 和 LinkedList 的区别?
- HashMap 的底层实现?
- HashMap 1.7 和 1.8 的区别?
- HashMap 如何解决哈希冲突?
- HashSet 的底层实现?
- ConcurrentHashMap 的底层实现?
- Hashtable 和 HashMap 的区别?
- TreeMap 和 HashMap 的区别?
- WeakHashMap 的特点?
- CopyOnWriteArrayList 的应用场景?
- LinkedHashMap 的底层原理?
- Map 的 key 是否可以为 null?
- ArrayList 扩容机制?
- Vector 和 ArrayList 的区别?
- PriorityQueue 的底层实现?
- BlockingQueue 的几种实现类?
- HashMap 死循环问题出现在哪个版本?
- fail-fast 和 fail-safe 的区别?
五、并发编程(JUC)
- Java 中的线程生命周期?
- Runnable 和 Callable 的区别?
- ThreadPoolExecutor 的参数有哪些?
- 线程池的拒绝策略有哪些?
- 创建线程的四种方式?
- synchronized 的底层实现原理?
- synchronized 和 ReentrantLock 的区别?
- 公平锁和非公平锁的区别?
- AQS 的原理?
- CountDownLatch 的应用场景?
- CyclicBarrier 的应用场景?
- Semaphore 的应用场景?
- Exchanger 的应用场景?
- volatile 能保证原子性吗?
- CAS 的原理?
- ABA 问题是什么?怎么解决?
- ThreadLocal 的作用及原理?
- ThreadLocal 内存泄漏的原因?
- JUC 中的原子类有哪些?
- 乐观锁和悲观锁的区别?
六、JVM & 内存模型
- JVM 内存结构?
- 堆和栈的区别?
- 堆内存的分代模型?
- 新生代、老年代、永久代的区别?
- 元空间和永久代的区别?
- 对象在堆中的分配过程?
- 对象什么时候进入老年代?
- JVM 的垃圾回收算法?
- 垃圾收集器有哪些?
- CMS 和 G1 的区别?
- Minor GC 和 Full GC 的区别?
- 什么是 Stop The World?
- JVM 常见的调优参数?
- 类加载的双亲委派机制?
- 双亲委派的好处?
- 类加载器的种类?
- OOM 的常见类型?
- 内存泄漏和内存溢出的区别?
- finalize() 方法的作用?
- Java 内存模型(JMM)的核心内容?
七、泛型、注解、反射
- 什么是泛型?
- 泛型的类型擦除机制?
- 泛型通配符
? extends
和? super
的区别? - 注解的作用是什么?
- 元注解有哪些?
- 自定义注解如何实现?
- 反射的作用?
- 反射的性能问题?
- 反射能否访问私有字段?
- 动态代理的两种实现方式?
- JDK 动态代理和 CGLIB 的区别?
- 反射如何创建对象?
- Class.forName 和 ClassLoader.loadClass 的区别?
- 注解和反射结合能实现哪些功能?
- 注解处理器(APT)的作用?
八、Java 8+ 新特性
- Lambda 表达式的作用?
- 函数式接口有哪些?
- Stream 流的常见操作?
- Optional 的作用?
- CompletableFuture 的作用?
- Java 8 接口的默认方法和静态方法?
- forEach 和传统 for 的区别?
- parallelStream 的原理?
- Java 9 模块化系统(Jigsaw)是什么?
- Java 14 的 switch 表达式增强?
- Java 16 的 record 特性?
- Java 17 的 sealed class 特性?
- var 关键字是什么?
- ZGC 的特性?
- Project Loom 的虚拟线程是什么?
九、IO
好 — 我把范围 大幅扩展并细化,把 Socket / TCP / UDP / TLS / WebSocket / 内核调优 / 实战题 / 编码题 都加进来了。现在给你一个160 道题目清单(按主题分组),覆盖面更广、更细致,便于你当面试题库或复习大纲使用。
Java IO & Socket 深度面试题(160题)
一、Java IO 基础与流(1–20)
- Java IO 的总体体系结构是什么?
- InputStream / OutputStream 的常见子类有哪些?
- Reader / Writer 的常见子类有哪些?
- 字节流与字符流的本质区别和使用场景?
- 为什么要有缓冲流(BufferedInputStream / BufferedReader)?
- flush() 与 close() 的区别与细节(含异常处理)?
- try-with-resources 的工作原理(AutoCloseable、suppressed exceptions)?
- 装饰器模式在 Java IO 中如何体现?举例链式包装。
- File 类的能力与限制(能否读写数据、元数据操作)?
- RandomAccessFile 的用途、指针机制与并发注意点?
- FileInputStream 与 FileReader 有什么差别?何时用哪个?
- 常见的文件读写拷贝实现方式(read/write、buffered、NIO transferTo)性能对比。
- 如何安全地读写文本并处理字符编码(InputStreamReader/OutputStreamWriter)?
- 字符编码问题常见坑(UTF-8 与 GBK 混用、BOM、截断)如何避免?
- 文件追加(append)实现方式与原子性问题。
- 如何判断并处理文件是否存在、权限、是否被占用?
- File.mkdirs() 与 mkdir() 的区别与返回值含义。
- 临时文件(File.createTempFile)与删除策略。
- 文件的读写锁与并发访问策略(何时需要外部锁)。
- 流关闭异常处理的最佳实践。
二、Java IO 设计与模式(21–40)
- InputStream/OutputStream 的装饰器链常见组合(Buffered -> GZIP -> Cipher 等)。
- PushbackInputStream、SequenceInputStream 的作用与应用场景。
- PipedInputStream / PipedOutputStream(管道流)的使用与线程注意点。
- FilterInputStream/FilterOutputStream 的设计与扩展。
- IO 与异常处理:IOException 的常见子类与处理策略。
- 流复制的常见实现模板(模板方法式代码)。
- NIO 引入前的 IO 局限性(可扩展性、线程模型)。
- 如何实现一个带超时的读操作(Socket/Channel)?
- 流式处理与内存友好型处理(流式处理大文件)实践。
- Base64 编解码在流中的高效集成方法。
- 加密/解密流(CipherInputStream/CipherOutputStream)如何正确关闭?
- GZIPInputStream/GZIPOutputStream 的压缩流使用注意事项。
- ObjectInputStream/ObjectOutputStream 的工作机制(类元数据、句柄表)。
- 如何实现跨语言的序列化兼容(JSON/Protobuf/Avro)?
- 实现自定义 InputStream 子类时需要注意什么(read 方法语义)?
- 设计用于日志写入的高吞吐 IO 模式(异步批量写)。
- 流复制时如何统计速率与进度(带进度回调)?
- 如何安全处理二进制文件(流边界、magic header)?
- 大对象/大数组写入流时的内存优化策略(分块、流化)。
- 如何实现可重入/可恢复的断点续传文件写入?
三、文件系统、锁与操作(41–60)
- Java 中文件锁 FileLock 的类型(共享/独占)及实现原理。
- FileLock 的局限性(跨 JVM、跨 OS 行为差异)。
- 文件描述符泄露的常见原因与定位方法。
- 文件句柄上限(ulimit)对 Java 服务的影响与排查。
- 硬链接与软链接在 Java 中如何区分与操作?
- MappedByteBuffer(内存映射文件)的优劣与风险(内存回收、文件锁)。
- 大文件分片读取与并发合并策略。
- 如何高效统计大日志文件中某条件的行数(分块 + 并行)?
- 文件系统缓存(PageCache)对读写性能的影响机制。
- fsync / FileDescriptor.sync 在持久化保障上的作用。
- 文件顺序写与随机写的性能差异与优化建议。
- 磁盘类型(SSD vs HDD)对 IO 策略的影响。
- 原子重命名(renameTo/Files.move)的跨平台差异。
- 文件监控(WatchService)的实现限制与替代方案。
- 处理日志切割(rotation)时的文件句柄管理策略。
- 如何实现零停机部署中对文件的平滑迁移?
- 软删除(标记删除)与物理删除的 IO 考量。
- 备份/快照策略对 IO 的影响(冷备 vs 热备)。
- 大文件校验(MD5/SHA)在流式处理中的实现。
- 文件系统一致性问题(写入后立即读取到不同步)如何诊断。
四、序列化与反序列化(61–75)
- Java 原生序列化(Serializable)的机制和对象写入格式。
- serialVersionUID 的作用、自动生成与兼容性策略。
- transient 字段、static 字段在序列化中的处理。
- Externalizable 与 Serializable 的区别与使用场景。
- Java 序列化的安全风险(反序列化漏洞)与防护措施。
- 高性能二进制序列化方案对比:Kryo、Protostuff、Protobuf、Avro。
- JSON 和二进制序列化的权衡(可读性 vs 性能/大小)。
- 如何实现可演化的序列化协议(向前/向后兼容)?
- 对象图序列化时循环引用的处理(句柄机制)。
- 对象序列化性能调优要点(缓冲、对象重用)。
- 在分布式系统中如何管理序列化策略(跨服务版本)?
- 自定义序列化(writeObject/readObject)常见陷阱。
- 序列化时如何处理类加载器问题?
- 大对象序列化时的内存与 GC 风险如何降低?
- 使用序列化作为缓存(Redis/Memcached)时的注意事项。
五、NIO 深入:Buffer / Channel / Selector(76–100)
- NIO 三大核心(Buffer、Channel、Selector)各自职责是什么?
- ByteBuffer 的 position / limit / capacity 三指针语义详解。
- flip(), clear(), rewind(), compact() 各自什么时候用?
- DirectByteBuffer 与 HeapByteBuffer 的底层差异与性能影响。
- ByteBuffer 的切片(slice)与只读视图(asReadOnlyBuffer)。
- Scatter/Gather IO(分散读取/聚集写入)的使用场景与实现。
- FileChannel 的 position、size、truncate 等方法详解。
- MappedByteBuffer(内存映射)在 NIO 中如何创建与释放?
- Channel 与传统 Stream 的区别(阻塞/非阻塞、文件描述符共享)。
- Selector 的工作流程:注册、选择、取消、keys、selectedKeys。
- Selector 的底层实现依赖(select/poll/epoll/kqueue)及其影响。
- 非阻塞 Channel 的 read/write 返回值语义(-1、0、>0)。
- NIO 中的粘包/拆包问题与常见解决策略(定长/分隔/长度前置)。
- 如何在 NIO 中实现超时断开和心跳检测?
- Pipe(管道)在 NIO 中的用途与限制。
- 多 Selector / 多 Reactor 的线程模型设计要点。
- ByteBuffer 内存可见性与多线程访问问题(不是线程安全)。
- 避免 NIO 频繁创建 DirectByteBuffer 的垃圾和内存泄露策略。
- 使用 Selector 时的遍历与删除 selectedKeys 的正确姿势。
- NIO 读写循环的高效实现模板。
- Channel.transferTo/transferFrom 的零拷贝含义与局限。
- NIO 与 FileChannel 实现文件间高效复制的流程。
- 使用 NIO 实现高并发服务器时的常见瓶颈。
- NIO 在 Windows 与 Linux 上的行为差异(可伸缩性影响)。
- 实战题:实现一个使用 NIO 的简单 echo 服务器(思路要点)。
六、Socket / TCP / UDP / WebSocket(101–140)
- Socket 的基本概念:端点、三元组/四元组(IP:port + peer)。
- Java 中 Socket、ServerSocket、DatagramSocket 的主要 API 区别。
- TCP 与 UDP 的核心差异(可靠性、有序性、连接性)。
- TCP 三次握手(SYN、SYN-ACK、ACK)与四次挥手流程详解。
- TIME_WAIT、CLOSE_WAIT、FIN_WAIT1/2 等 TCP 状态含义与产生原因。
- 半开连接(half-open)是什么,如何检测与恢复?
- TCP 的流量控制(窗口)与拥塞控制(慢启动、拥塞避免、快重传、快恢复)基础。
- Nagle 算法(TCP_NODELAY)的原理和在延迟场景下的影响。
- TCP 延迟确认(delayed ACK)对交互型应用的影响。
- Socket 选项 SO_TIMEOUT、SO_KEEPALIVE、SO_REUSEADDR、SO_REUSEPORT、SO_LINGER 含义与使用场景。
- backlog 参数(ServerSocket 构造或 listen)与 accept 队列(syn, accept 队列)区别。
- ephemeral port(短暂端口)与端口耗尽问题及解决办法。
- SYN flood 攻击的原理和防护(SYN cookies、firewall)。
- TCP 快速打开(TCP Fast Open)是什么,有何优劣?(简述)
- MTU、MSS 与 IP 分片对传输的影响与诊断方法。
- UDP 的组播(Multicast)与广播(Broadcast)机制与 Java 支持(MulticastSocket)。
- UDP 丢包、乱序、包大小限制(最佳实践)。
- UDP 穿透 NAT(STUN/ICE)的基本原理。
- 如何在 Java 中实现高性能 UDP 服务器?(NIO + DatagramChannel)
- WebSocket 协议基础(握手、帧格式、ping/pong、close)。
- Java 实现 WebSocket 的常见库(javax.websocket、Netty websocket、Undertow)。
- TLS over TCP(HTTPS)的握手流程要点(证书验证、对称密钥协商)。
- 使用 SSLSocket / SSLServerSocket 和 SSLEngine 的差别及适用场景。
- SSL/TLS 握手的重用(session resumption)与性能优化。
- 中间人攻击(MITM)与证书链、CA、信任根的角色。
- 如何在 Socket 程序中实现心跳、超时与断线重连?
- TCP Keepalive 与应用层心跳的区别和协同使用。
- 如何通过抓包(tcpdump/wireshark)诊断 Socket 连接问题?
- Socket 的非阻塞 accept/read/write 实现注意点(资源/异步安全)。
- 如何避免 TCP 粘包/拆包在 Socket 原生编程中的影响(流式协议设计)?
- 实战:实现一个带长度前缀的 TCP 协议的 Java 服务端/客户端(思路)。
- 如何优雅处理大量短连接的场景?(连接池、HTTP keep-alive)
- Socket 端口复用(SO_REUSEADDR vs SO_REUSEPORT)在负载均衡中的用法。
- 多路复用(select/poll/epoll)与 socket 大连接数的处理。
- 网络字节序(big-endian)与数据编解码注意点。
- 使用 TCP_NODELAY(禁用 Nagle)时的 CPU/网络 trade-off。
- 如何在 Java 中做到零拷贝文件传输(Socket + FileChannel.transferTo)?
- Socket 关闭流程中遇到阻塞(SO_LINGER)的处理办法。
- 如何防止或检测 “socket half-closed” 的资源泄漏?
- 实战题:用 BIO、NIO、Netty 各实现一个简易的聊天服务器,比较性能与代码复杂度(思路要点)。
七、异步 IO、IO 模型与高阶模式(141–150)
- IO 模型分类:同步阻塞、同步非阻塞、IO 多路复用、信号驱动、异步(AIO)。
- Reactor 模式与 Proactor 模式的原理与区别。
- Java AIO(AsynchronousChannel、AsynchronousSocketChannel)API 介绍。
- CompletionHandler 与 Future 风格的异步回调比较。
- 高并发下选择 NIO 还是 AIO 的实际考量(实现复杂度、平台支持)。
- 事件驱动系统的 back-pressure 设计(流控、速率限制)。
- 线程模型设计:acceptors / io workers / business workers 的权衡。
- RCU、无锁队列在高并发 IO 框架中的应用场景。
- 如何在应用层实现请求队列与排队策略来缓解突发流量?
- 实战题:设计一个支持百万连接的服务器架构(核心组件与 IO 策略)。
八、零拷贝、内核与性能调优(151–160)
- 零拷贝(zero-copy)概念和常见实现(sendfile, mmap, splice)。
- FileChannel.transferTo/transferFrom 在 Linux 下如何利用 sendfile 实现零拷贝?
- mmap(内存映射文件)的实现原理与 GC /回收问题。
- DMA(Direct Memory Access)在零拷贝中的作用与限制。
- 内核态 / 用户态拷贝次数与零拷贝带来的减少效果。
- TCP 窗口、拥塞控制调优参数(net.ipv4.tcp_* 系列常见项)。
- 系统级调优:文件描述符上限(ulimit -n)、somaxconn、backlog、epoll_limits 等。
- 高并发网络服务的监控指标(fd 使用、accept latency、context switch、cpu steal)。
- 性能诊断工具与流程:iostat, vmstat, sar, perf, tcpdump, ss, strace。
- 实战题:如何定位并修复一个高并发服务器的 accept 阻塞 / 连接丢失 问题(诊断步骤)。
如果你愿意,我可以 接着把每一道题写成“面试级详细解析”:每题包含 —— 概念简述 + 原理/底层机制 + 常见面试追问点 + 典型代码/伪代码(若适用) + 常见坑与性能考量。
为了不把单条回复弄得太长,我建议按批次输出解析(例如每批 15–20 题)。
下面我 先示范性地把第 1–10 题写成详细解析,风格如下(概念 + 要点 + 代码/命令 + 面试追问点)。如果你满意这个格式,我会继续按相同风格往下写每批题的解析。
示例:第1–10题 详细解析(示范风格)
1. Java IO 的总体体系结构是什么?
概念
Java IO 可以分为两大类:传统阻塞 IO(java.io)和 NIO(java.nio)。传统 IO 提供字节流(InputStream/OutputStream)、字符流(Reader/Writer)、以及各种装饰器(Buffered、Data、Object 等)。NIO 提供 Buffer、Channel、Selector 及更底层的文件映射(MappedByteBuffer)。
原理 / 要点
- 传统 IO 基于流(Stream)抽象,面向流式、同步、阻塞操作。
- NIO 引入 Buffer(内存块)和 Channel(可读写的端点),支持非阻塞和多路复用(Selector),更适合高并发网络 I/O。
- Java 还有 AIO(Asynchronous IO)作为异步回调风格的补充(java.nio.channels.AsynchronousChannel)。
面试追问点
- 什么时候用阻塞 IO,什么时候用 NIO?
- NIO 能否替代传统 IO?(部分场景:文件流仍然方便)
- AIO 与 NIO 的区别是什么?
2. InputStream / OutputStream 的常见子类有哪些?
要点列举(常见)
- FileInputStream / FileOutputStream(文件字节流)
- ByteArrayInputStream / ByteArrayOutputStream(内存字节流)
- BufferedInputStream / BufferedOutputStream(缓冲)
- DataInputStream / DataOutputStream(按基本类型读写)
- ObjectInputStream / ObjectOutputStream(对象序列化)
- PipedInputStream / PipedOutputStream(管道,用于线程间通信)
面试追问点
- 什么时候使用 Buffered 流?(I/O 较小且频繁时)
- DataOutputStream 写的二进制如何跨平台读?(须约定端序)
3. Reader / Writer 的常见子类有哪些?
要点列举(常见)
- FileReader / FileWriter(字符文件)
- InputStreamReader / OutputStreamWriter(字节流 ↔ 字符流 适配器)
- BufferedReader / BufferedWriter(字符缓冲)
- CharArrayReader / CharArrayWriter(基于字符数组)
面试追问点
- 为什么不用 FileReader 直接读取所有文本?(编码问题)
- InputStreamReader 如何指定编码?
4. 字节流与字符流的本质区别和使用场景?
概念
- 字节流(InputStream/OutputStream)以字节为单位处理数据,适用于二进制数据(图片、音频、视频、压缩包)。
- 字符流(Reader/Writer)以字符为单位,做了字符解码/编码,适合处理文本。字符流基于字符集(charset),内部通常由 InputStreamReader/OutputStreamWriter 做转换。
面试追问点
- 处理 UTF-8 文件时用哪种流?(用字节流 + 指定 InputStreamReader 编码,或者 Files.newBufferedReader(Path, Charset))
- 字符流会不会丢失数据?(若编码/解码不一致会)
5. 为什么要有缓冲流(BufferedInputStream / BufferedReader)?
要点
- 减少系统调用次数(read 系统调用代价高),提升 I/O 效率。
- 提供按行读取、标记/重置等高级功能(BufferedReader.readLine(), mark/reset)。
- 对小、频繁的读写操作特别有效。
代码示例
1 | try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) { |
面试追问点
- BufferedInputStream 的默认缓冲区大小是多少?(通常 8KB,但可构造时指定)
- 缓冲流什么时候可能不适合使用?(极低延迟、实时数据场景)
6. flush() 与 close() 的区别与细节(含异常处理)
要点
flush()
:将缓冲区中的数据强制写出到底层流,但流仍然可用。适合在不关闭流的情况下确保数据发送。close()
:先flush()
,再释放资源(关闭底层描述符),关闭后不能再写入。close()
可能抛出异常,要在 finally 或 try-with-resources 中处理。- try-with-resources 会自动调用 close,并会将 close 抛出的异常作为 suppressed exception 记录。
面试追问点
- 为什么要显式 flush?(网络协议的即时发送、日志写入保证等)
- close 抛出异常时如何处理 suppressed exception?(在 Java 里可通过 Throwable.getSuppressed() 查看)
7. try-with-resources 的工作原理(AutoCloseable、suppressed exceptions)
要点
- Java 7 引入,任何实现
AutoCloseable
的对象都可以在 try(…) 中声明,try 结束时会自动调用close()
。 - 若 try 块中抛出异常,close 抛出异常会被作为 suppressed 添加到初始异常上,不会覆盖初始异常。
- 编译器把 try-with-resources 翻译为 finally 块并处理 suppressed。
面试追问点
- AutoCloseable 与 Closeable 的区别?(Closeable 的 close() 声明抛 IOException;AutoCloseable 更通用)
- 多个资源按什么顺序关闭?(与声明顺序相反,即后声明先关闭)
8. 装饰器模式在 Java IO 中如何体现?举例链式包装
要点
- Java IO 将基础流包装成更复杂功能的流:例如
new BufferedReader(new InputStreamReader(new FileInputStream(path)))
,每一层都是装饰器,扩展了读取能力(缓冲、解码)。 - 装饰器允许在不改变原始类的情况下添加行为,且可任意组合。
面试追问点
- 装饰器带来的缺点?(层级过深可读性差、异常处理复杂)
9. File 类的能力与限制(能否读写数据、元数据操作)
要点
java.io.File
主要用于表示路径和进行文件/目录的元数据操作(exists, isDirectory, length, renameTo 等),它 不直接读写内容。- 读写需通过流(FileInputStream/FileOutputStream/FileChannel 等)或 NIO 的 Files 工具类。
File
的 renameTo 在不同平台上语义不一致(原子性不可保证)。
面试追问点
- Java 7 后 Files / Path 提供哪些增强?(更丰富的原子操作、拷贝、移动、文件属性)
10. RandomAccessFile 的用途、指针机制与并发注意点
要点
RandomAccessFile
允许在文件中任意位置读取/写入,通过seek(long pos)
移动文件指针。- 适合索引文件、实现简单的数据库、文件内插入/覆盖等。
- 不是线程安全的;多个线程共享同一个 RandomAccessFile 需外部同步或为每线程打开独立实例(但会增加文件描述符消耗)。
代码示例
1 | try (RandomAccessFile raf = new RandomAccessFile("data.bin", "rw")) { |
面试追问点
- RandomAccessFile 与 FileChannel 的关系?(RandomAccessFile.getChannel() 可获取 Channel;NIO 更现代)
如果这个示例的详细程度和格式符合你的期望,我会接着按相同格式把 第11–30、31–50……分批完整输出,直到把所有 160 题都写成“面试级详解”。我建议每批 15–20 题,这样查阅方便,也便于你随时检索或要求调整深度。
要我继续把接下来的题(11–30)写成详细解析吗?我可以现在开始并把下一批(11–30,共20题)一次性发给你。
当然可以 👍
Java 基础的面试题非常庞杂,我可以帮你继续扩展,补充到更多的维度,甚至超过 200+ 道。之前我给你整理了 150 道,下面我会在原有的基础上,继续补充更多(会覆盖一些面试官经常“刁钻”考的点、源码、陷阱题、实战场景):
Java 基础面试题(继续扩展)
一、语言特性 & 基础语法
- Java 中的
==
和equals()
有什么区别?如何正确重写equals()
? - 为什么重写
equals()
时必须重写hashCode()
?不重写会怎样? Object
类有哪些方法?哪些方法经常需要重写?final
、finally
、finalize()
有什么区别?- Java 中的
instanceof
关键字是如何实现的? - 接口和抽象类的区别?接口能不能有构造函数?
- 接口可以多继承吗?抽象类可以实现接口吗?
default
方法和static
方法在接口中的意义是什么?- 什么是内部类?分为哪几种?(成员内部类、静态内部类、局部内部类、匿名内部类)
- 匿名内部类和 Lambda 表达式的区别?
二、数据类型 & 内存
- Java 中的基本数据类型和包装类型有哪些区别?
- 为什么
Integer
有缓存机制?范围是多少? - 为什么
new Integer(127) == new Integer(127)
为 false,而Integer.valueOf(127) == Integer.valueOf(127)
为 true? NaN
和Infinity
在 Java 中如何表示?- 为什么浮点数计算有精度问题?如何避免?
BigDecimal
为什么能解决浮点数精度问题?- Java 中的字符集默认是什么?在不同平台上会不一样吗?
String
、StringBuilder
、StringBuffer
区别?线程安全性?- 为什么
String
是不可变的?背后实现细节? intern()
方法的作用是什么?
三、集合框架
ArrayList
和Vector
的区别?- 为什么
ArrayList
的扩容是1.5 倍
? LinkedList
是双向链表还是单向链表?HashSet
底层是如何实现的?TreeSet
和TreeMap
的底层实现是什么?PriorityQueue
的底层数据结构是什么?ConcurrentSkipListMap
的底层原理是什么?CopyOnWriteArrayList
的写时复制是怎么实现的?有什么优缺点?EnumMap
和EnumSet
是什么?为什么效率高?WeakHashMap
和HashMap
的区别?
四、异常 & 错误处理
- Java 中的异常分为哪两大类?
Error
和Exception
的区别?- 受检异常和非受检异常区别?
throw
和throws
有什么区别?try-with-resources
的底层原理是什么?- 自定义异常如何设计?继承哪个类?
- 为什么不建议捕获
Throwable
? - 异常链(Exception Chaining)是什么?
- Java 8 之后对异常处理有改进吗?
- 如何在多线程环境下正确传递异常?
五、JVM & 内存管理
- Java 内存模型(JMM)是什么?
- JVM 内存分为哪几个区域?每个区域的作用是什么?
- 为什么
String
常量池要放在堆里而不是方法区? - 什么是垃圾回收(GC)的可达性分析算法?
- 哪些对象可以作为 GC Roots?
- 强引用、软引用、弱引用、虚引用的区别?
- GC 有哪些算法?标记-清除、标记-整理、复制算法区别?
- 常见的垃圾收集器有哪些?G1、ZGC、Shenandoah 有什么特点?
- 什么是 Stop-The-World (STW)?
- JVM 调优常见参数有哪些?
六、并发编程基础
- Java 中的
volatile
关键字的底层实现原理? synchronized
的实现原理?对象头和 Monitor 是什么?- 偏向锁、轻量级锁、重量级锁的区别?
- 什么是 CAS 操作?ABA 问题如何解决?
- 什么是线程安全?举例哪些类是线程安全的?
- ThreadLocal 的底层原理是什么?为什么会有内存泄漏问题?
- 线程池的核心参数有哪些?
- 什么是拒绝策略?
- Executors 提供的几种常见线程池?
Future
和CompletableFuture
区别?
七、编译 & 运行时
- Java 的反射机制是什么?底层原理?
- 为什么反射性能差?JDK 9 之后如何优化?
- 什么是动态代理?JDK 动态代理和 CGLIB 区别?
- 注解的底层原理是什么?运行时注解如何实现?
- ClassLoader 的双亲委派模型是什么?
- 如何打破双亲委派?
- 什么是 SPI(Service Provider Interface)机制?
- Java 是如何实现跨平台的?
- JIT(即时编译器)优化了哪些东西?
- 什么是逃逸分析?
八、常见场景 & 设计
- 单例模式在 Java 中有哪些实现方式?哪种最优?
- 懒汉模式和饿汉模式的区别?
- 枚举单例为什么是最佳实践?
- 为什么要用工厂模式?
- 装饰器模式和代理模式区别?
- Java 中的事件监听模型是怎么实现的?
- 为什么 Java 不支持多继承?
- 如何用 Java 实现回调机制?
- Java 中如何实现观察者模式?
Optional
的设计初衷是什么?
二、Java 并发编程(30题)
- 线程的生命周期?
- 创建线程的方式有哪些?
- Runnable 和 Callable 的区别?
- Future 和 CompletableFuture 的区别?
- ThreadLocal 原理?
- synchronized 的锁优化机制?
- ReentrantLock 的实现原理?
- 公平锁和非公平锁区别?
- 乐观锁和悲观锁区别?
- AQS 原理?
- CountDownLatch 和 CyclicBarrier 区别?
- Semaphore 的实现原理?
- Exchanger 的使用场景?
- ForkJoinPool 的原理?
- CAS 操作的底层实现?
- ABA 问题是什么?如何解决?
- Atomic 原子类的原理?
- BlockingQueue 的实现原理?
- ConcurrentHashMap 的分段锁机制?
- ConcurrentHashMap 扩容机制?
- CopyOnWriteArrayList 的原理?
- 线程池的核心参数?
- 线程池的拒绝策略?
- 线程池的工作流程?
- ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 的区别?
- 为什么不建议使用 Executors 创建线程池?
- JMM 内存可见性问题?
- happens-before 规则?
- 死锁产生的条件?如何避免?
- 常见并发容器有哪些?区别?
三、JVM 与性能优化(30题)
- JVM 内存结构?
- 堆和栈的区别?
- 新生代、老年代、永久代(元空间)的区别?
- 对象的创建过程?
- 对象的内存分配策略?
- GC Roots 有哪些?
- 垃圾回收算法有哪些?
- CMS 垃圾回收器的工作流程?
- G1 垃圾回收器原理?
- ZGC 和 Shenandoah 的特点?
- Minor GC 和 Full GC 的区别?
- OOM 常见类型?如何排查?
- 类加载器的分类?
- 双亲委派模型的破坏场景?
- JVM 常见参数调优?
- 内存泄漏和内存溢出的区别?
- 逃逸分析是什么?
- 方法内联的原理?
- JIT 编译器的优化?
- Safepoint 机制?
- Stop The World 是什么?
- Finalize 方法的问题?
- JVM 如何判断对象可回收?
- TLAB 的作用?
- Direct Memory 为什么会 OOM?
- Metaspace 的作用?
- Full GC 触发条件?
- 如何查看 JVM 内存使用情况?
- Arthas 常用命令?
- JVM 调优常见面试题?
四、Spring & Spring Boot(30题)
- Spring 的 IoC 容器原理?
- Bean 的生命周期?
- BeanFactory 和 ApplicationContext 的区别?
- 单例 Bean 如何保证线程安全?
- Spring AOP 的实现原理?
- JDK 动态代理和 CGLIB 的区别?
- Spring 事务的传播机制?
- Spring 事务的隔离级别?
- @Transactional 的实现原理?
- 循环依赖如何解决?
- Spring 的三级缓存解决了什么问题?
- Spring MVC 的工作流程?
- DispatcherServlet 的作用?
- Spring Boot 的自动装配原理?
- Spring Boot Starter 的机制?
- @ConfigurationProperties 和 @Value 的区别?
- Spring Boot 如何实现热部署?
- Spring Boot 如何处理配置文件?
- @Conditional 注解的作用?
- Spring Boot 常见的优化点?
- Spring 中的事件机制?
- ApplicationListener 的使用?
- Spring 如何加载配置文件?
- Bean 的作用域有哪些?
- Spring 如何实现异步调用?
- Spring 中的 @Lazy 注解?
- Spring 的资源加载机制?
- Spring Boot 的内嵌容器有哪些?
- Spring Boot Actuator 的作用?
- Spring Boot 如何与 Docker/K8s 结合?
五、Spring Cloud(30题)
- Spring Cloud 是什么?
- Spring Cloud 和 Dubbo 的区别?
- Eureka 的工作原理?
- Eureka 的自我保护机制?
- Ribbon 的负载均衡策略?
- Ribbon 和 Nginx 的区别?
- Feign 的工作原理?
- Feign 如何支持熔断?
- Hystrix 的工作原理?
- Hystrix 的线程池隔离和信号量隔离?
- Resilience4j 和 Hystrix 区别?
- Gateway 的核心功能?
- Gateway 和 Zuul 的区别?
- Config Server 的作用?
- Config Server 如何保证配置实时刷新?
- Spring Cloud Bus 的原理?
- Sleuth 的作用?
- Sleuth 与 Zipkin 的关系?
- 分布式链路追踪的原理?
- Spring Cloud Stream 的作用?
- 消息驱动模型和事件驱动模型区别?
- Nacos 和 Eureka 的区别?
- Nacos 配置中心和注册中心的实现原理?
- Sentinel 的工作原理?
- 限流的常见算法(令牌桶、漏桶)?
- 熔断和降级的区别?
- CAP 定理和 Spring Cloud 的关系?
- Spring Cloud 与 Kubernetes 的关系?
- 微服务中的灰度发布?
- Spring Cloud 如何实现服务网格?
六、微服务架构 & 分布式系统(30题)
- 什么是微服务?
- 微服务与 SOA 的区别?
- 微服务的优缺点?
- 微服务的拆分原则?
- 如何保证微服务之间的数据一致性?
- 分布式事务有哪些解决方案?
- TCC、SAGA、XA 的区别?
- 本地消息表和可靠消息最终一致性?
- 分布式锁的实现方式?
- Redis 分布式锁的缺陷?
- Zookeeper 分布式锁的实现?
- 数据库分库分表策略?
- 水平拆分与垂直拆分区别?
- 分布式 ID 生成方案?
- 雪花算法的原理?
- API 网关的作用?
- 服务注册与发现机制?
- 微服务如何保证高可用?
- 微服务中的限流和熔断?
- 微服务如何保证安全性?
- OAuth2 的原理?
- JWT 的作用?
- 单点登录 SSO 的实现?
- 配置中心的作用?
- 微服务中的监控体系?
- Prometheus 的原理?
- ELK 在微服务中的应用?
- 服务调用链路追踪的意义?
- 服务雪崩效应是什么?
- 如何设计一个高并发的微服务系统?
七、数据库 & 缓存(30题)
- MySQL 的存储引擎区别?
- InnoDB 和 MyISAM 的区别?
- MySQL 的事务特性?
- MVCC 的实现原理?
- MySQL 的索引类型?
- 聚簇索引和非聚簇索引?
- B+ 树索引原理?
- 覆盖索引和回表?
- 索引下推优化?
- MySQL 的锁机制?
- 行锁和表锁区别?
- 间隙锁是什么?
- 死锁的排查方法?
- SQL 执行计划如何分析?
- 慢查询优化方法?
- 数据库分库分表的难点?
- 分布式事务如何保证?
- Redis 的数据结构?
- Redis 为什么快?
- Redis 的持久化机制?
- Redis 的过期策略?
- Redis 的内存淘汰机制?
- Redis 的主从复制原理?
- Redis 的哨兵机制?
- Redis 的集群模式?
- Redis 的分布式锁实现?
- Redis 的常见问题?
- 缓存穿透、击穿、雪崩?
- 如何保证缓存和数据库一致性?
- Redis 和本地缓存 Caffeine 的区别?
八、消息队列(30题)
- 为什么要使用消息队列?
- 消息队列的优缺点?
- RabbitMQ 的核心组件?
- RabbitMQ 的消息确认机制?
- Kafka 的核心概念?
- Kafka 的分区机制?
- Kafka 的副本机制?
- Kafka 的 ISR 原理?
- Kafka 的消费组原理?
- Kafka 的 offset 提交方式?
- Kafka 的高吞吐量原理?
- Kafka 的零拷贝机制?
- RocketMQ 的核心特点?
- RocketMQ 的顺序消息?
- RocketMQ 的事务消息?
- 如何保证消息不丢失?
- 如何保证消息不重复消费?
- 如何保证消息有序?
- 消息堆积的解决办法?
- 如何设计延迟队列?
- 死信队列的作用?
- 幂等性的实现方式?
- 消息队列的限流方式?
- 消息队列的监控?
- Kafka 和 RabbitMQ 的区别?
- Kafka 和 RocketMQ 的区别?
- Pulsar 的特点?
- 消息中间件如何保证高可用?
- 消息队列如何做水平扩展?
- 消息队列和事件总线的区别?
九、DevOps & 容器化(30题)
- CI/CD 的流程?
- Jenkins 的作用?
- Docker 的核心原理?
- Docker 镜像和容器的区别?
- Dockerfile 的常见指令?
- 镜像分层机制?
- 容器与虚拟机的区别?
- Kubernetes 的核心组件?
- Pod 的生命周期?
- Deployment 和 StatefulSet 区别?
- K8s 的服务发现机制?
- ConfigMap 和 Secret 的区别?
- Ingress 的作用?
- K8s 的水平扩缩容机制?
- K8s 的滚动更新机制?
- K8s 如何做灰度发布?
- K8s 的调度策略?
- K8s 的网络模型?
- Service Mesh 的作用?
- Istio 的工作原理?
- Helm 的作用?
- K8s 的存储方案?
- Prometheus 的监控体系?
- Grafana 的作用?
- K8s 的安全机制?
- 容器的资源限制?
- Docker Compose 的作用?
- DevOps 的最佳实践?
- GitOps 的理念?
- K8s 如何保证高可用?
十、场景设计题(30题)
- 如何设计一个秒杀系统?
- 如何设计一个短链系统?
- 如何设计一个分布式 ID 生成系统?
- 如何设计一个高可用缓存系统?
- 如何设计一个订单系统?
- 如何设计一个支付系统?
- 如何设计一个消息推送系统?
- 如何设计一个日志收集系统?
- 如何设计一个搜索引擎?
- 如何设计一个推荐系统?
- 如何设计一个分布式锁?
- 如何设计一个统一鉴权系统?
- 如何设计一个限流系统?
- 如何设计一个灰度发布系统?
- 如何设计一个电商购物车?
- 如何设计一个库存系统?
- 如何设计一个分布式文件存储系统?
- 如何设计一个微服务网关?
- 如何设计一个分布式任务调度系统?
- 如何设计一个监控报警系统?
- 如何设计一个高并发聊天室?
- 如何设计一个排行榜系统?
- 如何设计一个评论系统?
- 如何设计一个分布式事务系统?
- 如何设计一个高可用注册中心?
- 如何设计一个数据同步系统?
- 如何设计一个 API 限速系统?
- 如何设计一个高可用的支付网关?
- 如何设计一个跨境电商架构?
- 如何设计一个金融级别的微服务系统?