用 ⭐ 表示重要性,如果没有 ⭐,了解就好。
Java基础
01 基础概念
1. java的特点 ⭐⭐
Java 的主要特点是:跨平台、面向对象,并且拥有自动内存管理和垃圾回收机制。
2. 优势和劣势 ⭐⭐
Java 的优势是生态成熟、跨平台能力强、工程化和社区支持完善;劣势是运行时依赖 JVM,在启动速度和极致性能方面通常不如 C++ 等语言。
3. 为什么是跨平台的 ⭐
Java 能支持跨平台,主要依赖于 JVM:也就是只要不同的平台都有jvm,那么java代码就能在不同的平台上运行。
这是因为 java 使用jvm将平台的差异化给抹除了,也就是不同的平台安装的jvm不同,只是解释出来的机器码不同,但对于字节码文件这样的不同是无感的,在因此,java代码就是跨平台的。
4. JVM、JDK、JRE三者关系 ⭐⭐
- JVM:Java 虚拟机,是Java程序运行的环境。它负责将Java字节码(由Java编译器生成)解释或编译成机器码,并执行程序。
- JRE:Java 运行环境,包含 JVM 和运行 Java 程序所需的类库。
- JDK:Java 开发工具包,包含 JRE 以及编译器、调试器等开发工具。
5. JVM 是什么⭐️
JVM 是 java 虚拟机,主要工作是将这些字节码动态地解释或通过 JIT(即时编译器) 编译为当前操作系统和 CPU 能识别的本地机器指令并执行。此外,JVM 还包括了内存管理、垃圾回收等功能,还通过屏蔽底层平台差异实现 Java 的跨平台运行。
6.编译型与解释型语言
- 编译型语言:在程序执行之前,整个源代码会被编译成机器码或者字节码,生成可执行文件。执行时直接运行编译后的代码,速度快,但跨平台性较差。
- 解释型语言:运行时由解释器将代码逐步转换并执行,通常跨平台性较好,但执行效率相对较低。
这是传统分类,现代语言通常结合编译、解释和运行时优化机制,不能简单绝对划分。
7. 为什么java解释和编译都有⭐️⭐️
Java 源代码首先由 javac 编译为与平台无关的字节码;JVM 运行字节码时,既可以通过解释器解释执行,也可以通过 JIT 编译器将热点代码编译成本地机器码后执行。因此,Java 采用的是编译与解释相结合的执行方式。
8. 值传递和引用传递⭐️⭐️
在 Java 中,所有参数传递都是值传递,不存在真正的“引用传递”。本质上是因为
9. JIT ⭐⭐
JIT(即时编译器)是 JVM 的运行时编译器,会将执行频率较高的字节码编译成本地机器码并缓存,从而提高热点代码的执行效率。
02 数据类型
0. 基本数据类型和引用数据类型是什么?⭐⭐⭐
- 基本数据类型:变量直接表示数据值
- 引用数据类型:变量保存对象的引用,通过引用访问对象,包括类、接口、数组、枚举和
String等。
1. java 的八种基本数据类型
Java 有 8 种基本数据类型:
- 整数型:
byte、short、int、long - 浮点型:
float、double - 字符型:
char - 布尔型:
boolean
常见注意点:
- 整数默认类型是
int,long字面量需加L。 - 浮点数默认类型是
double,float字面量需加F。 char占 2 个字节,是无符号类型。boolean的具体存储大小由 JVM 实现决定,不能简单认为固定占 1 个字节。- 基本类型对应的包装类是:
Byte、Short、Integer、Long、Float、Double、Character、Boolean。
2. int 和 long 占多少位,多少个字节
int:32 位,占 4 个字节,取值范围为-2^31到2^31 - 1。long:64 位,占 8 个字节,取值范围为-2^63到2^63 - 1。
3. 数据类型转换方式
- 自动类型转换:小范围类型可以自动转换为大范围类型,例如
int转long。 - 强制类型转换:大范围类型转换为小范围类型时需要显式转换,可能发生数据溢出或精度丢失,例如
long转int。 - 字符串转数值可以使用
Integer.parseInt()、Double.parseDouble()等方法。
4. 数据类型转换会出现什么问题呢
将大范围类型转换为小范围类型时,可能发生数据溢出或精度丢失;浮点数参与运算时,也可能存在精度问题。引用类型转换还可能因为实际类型不匹配抛出 ClassCastException。
5. 为什么用 bigDecimal 而不是double⭐️
因为 double 会出现精度丢失问题,由于它执行的是二进制浮点运算,也就是说,double 只能精确表示能被 \(\sum_1^n{2^{-n}}\) 的组合,而无法被精确组合出来的只能近似表示。
金额等对精度要求高的场景应使用 BigDecimal,或使用整数表示最小货币单位。
6. 装箱和拆箱是什么⭐️
装箱和拆箱描述的是基本数据类型与它对应的包装数据类型的转换过程。
Java 支持自动装箱和拆箱,但频繁操作会产生额外的对象创建和拆箱开销,包装类为 null 时拆箱还会抛出 NullPointerException。
7. java为什么要有基本数据类型的包装类 ⭐⭐⭐
包装类可以将基本类型作为对象使用,支持泛型、集合、反射等场景,并且可以表示 null。基本类型则更适合直接进行数值运算,性能和内存开销通常更低。
8. integer 相比 int 有什么特点
int是基本数据类型,不能为null,适合直接进行数值运算。Integer是包装类,可以为null,可用于泛型和集合,但存在装箱、拆箱的开销。
9. Integer 的缓存机制⭐️⭐️⭐
为了提高性能和减少内存开销,Java 对 Integer(以及其他部分包装类如 Byte、Short、Long、Character)在一定数值范围内实现了对象缓存机制。
- 默认缓存范围:
-128到127(包含两端)(\(-2^7, 2^7 - 1\)) - 当使用
Integer.valueOf(int)或自动装箱创建Integer对象时,如果值落在该范围内,不会新建对象,而是直接返回缓存中的已有对象。 - 超出此范围才会创建新对象。
📌 注意:直接用 new Integer(100) 会绕过缓存,始终创建新对象!
10. 为什么设计这个缓存
缓存常用的小整数,可以减少频繁创建和销毁相同对象的开销;同时,相比每次直接创建新对象,复用缓存对象性能更好。
03 面向对象
1. 怎么理解面向对象呢?⭐️⭐️⭐️
面向对象(Object-Oriented Programming, OOP)是一种编程思想,它将现实万物抽象为“对象”。每个对象都有自己的属性和行为。面向对象以“对象”为中心,通过对象之间的交互来完成程序功能,相比传统的面向过程,可维护性、可扩展性更强,也更加规范。
2. 封装、继承、多态⭐️⭐️⭐️
- 封装:将对象的属性和行为封装在一起,隐藏内部实现细节,只对外提供接口。
- 继承:允许一个类继承另一个类的属性和方法,从而实现代码复用。
- 多态:父类或接口引用指向不同子类对象时,同一个方法表现出不同的行为。
3. 多态解决了什么问题
多态通过“抽象”实现了“解耦”,让程序能够对“扩展”开放,对“修改”关闭(即 OCP 开闭原则)。调用方只依赖父类或接口,新增实现类时通常不需要修改原有调用逻辑,从而提高代码的扩展性。
4. 面向对象的设计原则 ⭐️
核心目的:写出高内聚,低耦合,易维护,可拓展的代码
| 原则 | 核心思想 | 简单解释 |
|---|---|---|
| 单一职责原则(SRP) | 一个类只做一件事 | 如:员工类只管员工信息,不负责工资计算 |
| 开放封闭原则(OCP) | 对扩展开放,对修改关闭 | 通过接口/抽象类扩展功能,不改原有代码 |
| 里氏替换原则(LSP) | 子类可以替换父类而不影响程序正确性 | 正方形不能直接继承矩形(因为高度宽度必须相等,破坏了“改变宽高”的行为一致性) |
| 接口隔离原则(ISP) | 客户端不应依赖不需要的接口 | 接口要小而精,避免“胖接口” |
| 依赖倒置原则(DIP) | 高层模块不依赖底层细节,依赖抽象 | 用接口编程,而不是具体实现 |
| 最少知识原则(LoD) | 一个对象应尽量少了解其他对象 | 只和“朋友”交互,避免过度耦合 |
4. 重写和重载⭐️⭐️
- 重载:同一个类中方法名相同、参数列表不同,与返回值和访问修饰符无关,属于编译时多态。
-
重写:子类重新实现父类中允许重写的方法,方法名和参数列表必须相同,子类访问权限不能更严格,属于运行时多态。
-
private、static、final方法不能被重写。 static方法只能被隐藏。- 重写方法的返回类型可以相同,也可以是父类返回类型的子类型,即协变返回类型
8. 抽象类和接口的描述以及区别⭐️
抽象类用来描述多个类的共同属性和行为,抽象类可以有成员变量、构造方法、抽象方法和具体方法,只能单继承。
接口用来定义一组行为的规范,一个类可以实现多个接口。在 java8之前 接口只能由常量和抽象方法,在 java8 之后可以有默认方法和静态方法。Java 9 起:还支持 private 方法(用于辅助 default/static 方法),但不能定义构造方法
9. final 修饰符⭐️⭐️
final 是 Java 中的一个关键字,表示“不可变”或“不可继承/重写”,可用于类、方法、变量,作用如下:
- 修饰类:不能被继承。
- 修饰方法:不能被重写。
- 修饰变量:只能赋值一次。
10. 抽象类能加 final 修饰吗
不能。抽象类必须被继承,而 final 类不能被继承,两者的设计目的冲突。
11.String 为什么要被 final 修饰? ⭐⭐
- 为了线程安全:因为
String是不可变的,所以它是天然线程安全的。 - 支持字符串常量池(String Pool):Java 为了节省内存,会将字符串字面量存放在“字符串常量池”中。如果两个
String变量的值相同,它们会指向内存中的同一个地址。假设String是可变的,如果线程 A 修改了字符串的值,那么所有指向该地址的变量(比如线程 B 里的变量)都会跟着被改变。这会直接导致数据错乱,常量池也就失去了意义。 String被广泛用于 Java 的各种核心底层参数。不可变性能防止这些关键参数在运行时被恶意篡改。
12. 接口中可以定义那些方法⭐️
抽象方法是接口的核心,所有实现接口的类都必须重写这些方法,抽象方法默认是 public 和 abstract, 在接口中可以省略。
默认方法是在 java 8 中引入的,允许接口提供具体实现,实现类可以选择性的重写默认方法
静态方法也是在 Java 8 中引入的,他们属于接口本身,可以通过接口名直接调用。
私有方法是在 java 9 引入的,该方法存在的意义就是辅助默认方法以及静态方法,实现类是不能访问这些方法的。
public interface Animal(){
void makesound();
static void staticMethod(){
privateMethod();
}
private void privateMethod(){
pass;
}
}
接口不能定义构造方法,因为接口不能被直接实例化。
13. 为什么子类初始化都需要先调用父类的构造器呢?
子类对象包含父类继承的部分,必须先初始化父类部分,才能保证整个对象处于正确状态。若没有显式调用,编译器会默认调用父类的无参构造方法。
14.static 修饰的变量和方法有什么特点?⭐⭐⭐
静态(static)成员属于类本身,而不是类的实例(对象)。它们在内存中只有一份,被所有实例共享。
15. 什么是内部类
内部类就是定义在另一个类内部的类。它就是外部类的一个属性,也可以被 private, public 等修饰符修饰。
- 内部类是外部类的成员,可以像字段、方法一样被访问控制修饰符(
private、protected等)修饰。 - 它能访问外部类的所有成员(包括私有成员)。
- 编译后会生成独立的
.class文件,命名格式为:外部类$内部类.class
04. 深拷贝和浅拷贝
注意点:对象大部分情况都存在堆内存,而该对象的引用可以在栈区(局部变量表),也可以在堆区(静态变量)
1. 什么是浅拷贝⭐️
浅拷贝会创建一个新对象,并复制原对象的字段值:
- 基本类型字段复制值,互不影响。
- 引用类型字段复制引用,新旧对象共享同一个引用对象。
2. 什么是深拷贝⭐️
深拷贝不仅复制对象本身,还会递归复制其引用类型字段指向的对象,使新旧对象完全独立。
3. 实现深拷贝的3种方式⭐️
- 手动复制:创建新对象,并递归复制其引用类型字段,最直观、可控。
- 重写
clone():先执行浅拷贝,再手动克隆所有引用类型字段。 - 序列化后反序列化:要求对象及其引用对象都实现
Serializable,但性能开销较大,transient字段不会被复制。
实际开发中,通常根据对象结构手动复制,或使用对象映射工具完成深拷贝。
05 泛型
1. 什么是泛型⭐️
泛型(Generics)是 Java 提供的一种“参数化类型”机制,它允许类、接口、方法再定义时使用一个或者多个类型参数,这些类型参数在使用时可以被指定为具体的类型。
- 编译期类型安全:在编译期检查类型,减少运行时类型错误。
- 消除强制类型转换:取出元素时不需要强制类型转换。
- 提高代码复用性:一套代码适配多种类型
2. 泛型的上下界⭐️
泛型上下界用于限制类型参数的范围:
- 上界:
<T extends Number>,表示T必须是Number或其子类。 - 下界:
<? super Integer>,表示通配符类型必须是Integer或其父类。
3. 通配符是什么?⭐️
通配符 ? 表示未知类型,主要用于泛型方法参数。
<?>:未知类型,只能读取为Object,不能写入具体对象。<? extends T>:类型是T或其子类,适合读取数据。<? super T>:类型是T或其父类,适合写入T类型数据。
可以记忆为:PECS 原则:Producer Extends,Consumer Super。(我还专门写过一篇博客)
- 生产者使用
extends,安全读取。 - 消费者使用
super,安全写入。
06 对象
1. java中创建对象的方式有哪些⭐️
- 使用
new关键字。 - 使用反射,如
Constructor.newInstance()。 - 使用
clone(),基于已有对象创建副本。 - 使用反序列化,根据字节流恢复对象。
- 使用工厂方法创建对象,例如
getInstance()、valueOf()。
注意:
Class.newInstance()已被弃用,推荐使用Constructor.newInstance()。
2. new 出来的对象什么时候回收⭐️⭐️
当对象不再被任何 GC Roots 间接或直接引用,即变得不可达时,就具备了被垃圾回收的条件。具体何时回收由 JVM 的垃圾回收器决定,无法手动精确控制。
3. 如何获取私有成员⭐️⭐️
正常情况下,private 成员只能在声明它的类内部访问。可以通过以下方式访问:
- 使用类提供的公开方法,例如 getter。
- 使用反射获取私有成员,并调用
setAccessible(true)或使用trySetAccessible()。
07 反射
1. 什么是反射⭐️⭐️⭐️
反射是 Java 提供的运行时机制,可以在程序运行期间获取类的信息,并动态创建对象、调用方法和访问字段,即使编译时不确定具体类型。
Class<?> clazz = Class.forName("com.example.Person");
// 创建对象
Object obj = clazz.getDeclaredConstructor(String.class)
.newInstance("张三");
// 调用方法
Method method = clazz.getMethod("sayHello");
method.invoke(obj);
// 访问字段
Field field = clazz.getDeclaredField("age");
field.setAccessible(true);
field.set(obj, 25);
2. getDeclared xxx 和 get xxx 的区别⭐️⭐️
getDeclaredXxx():只获取当前类声明的成员,包括私有成员,但不包括从父类继承的成员。getXxx():获取当前类及父类中声明的public成员。
3. 反射的应用常见
反射常用于:Spring、MyBatis 等框架在程序运行时动态的创建对象和调用方法等。
08 注解
1. 什么是注解⭐️⭐️⭐️
注解(Annotation)是 Java 5 引入的一种元数据形式,用于为代码提供附加信息(metadata),这些信息可以被编译器、开发工具或运行时环境读取和处理,但不会直接影响程序逻辑。
2. 注解的原理是什么?
注解信息可以根据保留策略在源码、字节码或运行时保留:
- 编译期注解处理器可以读取注解并生成代码,例如 Lombok。
- 运行时注解可以通过反射读取,并被 Spring、JUnit 等框架处理。
SOURCE注解编译后丢弃,CLASS注解保留在字节码中但运行时不可获取,RUNTIME注解可以在运行时通过反射获取。
3. 什么是元注解?
元注解是用于修饰注解的注解,常见的有:
@Retention:指定注解的保留策略。@Target:指定注解可以使用在哪些程序元素上。@Documented:指定注解是否包含在 JavaDoc 中。@Inherited:指定类上的注解是否可以被子类继承。@Repeatable:指定注解是否可以在同一位置重复使用。
09 异常
1. 介绍一下 java 异常⭐️⭐️⭐️
Java 异常用于表示程序运行过程中发生的非正常情况,异常体系的根类是 Throwable,主要分为 Error 和 Exception。
Error:表示 JVM 或系统层面的严重错误,通常难以恢复,例如OutOfMemoryError、StackOverflowError。Exception:表示程序运行过程中可以处理的问题。
Exception 又分为:
- 受检异常:除
RuntimeException外的Exception子类,编译器要求必须处理或声明抛出,例如IOException。 - 非受检异常:
RuntimeException及其子类,编译器不强制处理,例如NullPointerException、IndexOutOfBoundsException。
2. java 异常处理有哪些方式⭐️⭐️⭐️
异常处理主要是通过 try-catch 语句块来捕获和处理异常的,java 中常用的异常处理方式主要有:
- try catch 语句块:捕获并处理异常
- throw:在代码中主动抛出异常
- throws:在方法签名中声明可能抛出的异常,将异常交给调用者处理
3. try catch 语句的运行情况
try 块中的代码按顺序执行,一旦抛出异常,将在 catch 块中进行匹配与处理,然后程序将继续执行 catch 块之后的代码,如果没有匹配的代码块,那么异常将会被向上抛出。
- “
finally一定会执行吗?—— 不一定!如System.exit()、JVM 崩溃时不会执行。” - “
catch块只能捕获自身类型或者子类的异常”
多个 catch 块中,只有第一个与异常类型兼容的会被执行,其余全部跳过;因此应遵循“子类异常在前,父类异常在后”的顺序,否则会导致编译错误或逻辑错误。
4. try{return “a”} finally{return “b”}这条语句返回啥
返回b
5. 为什么设计受检异常
Java 早期希望通过编译期强制开发人员处理 I/O、网络等“可恢复错误”,提升程序健壮性。但现代观点认为这增加了 API 复杂度,因此很多框架(如 Spring JDBC)将受检异常封装为运行时异常。
6. 说出几个常见的运行时异常?
NullPointerException:对null对象进行操作。IndexOutOfBoundsException:访问不存在的索引。ClassCastException:对象类型强制转换失败。ArithmeticException:发生非法算术运算。IllegalArgumentException:方法参数不合法。NumberFormatException:字符串无法转换为数字。
7. 如何解决空指针异常?
针对 NPE:使用 Java 8 的 Optional、进行非空校验(if (obj != null))或使用 @NonNull 注解。
10 Object
1. object类有那些方法 ⭐️⭐️⭐️
Object 是 Java 中所有类的直接或间接父类,提供了 11 个核心方法,是对象行为的基础。其中最常用的是 equals()、hashCode()、toString() 和 clone() 等,开发者常需重写这些方法以实现自定义逻辑。
equals():比较对象是否相等,默认比较对象引用。hashCode():返回对象的哈希值。toString():返回对象的字符串表示。getClass():获取对象运行时的Class对象。clone():创建对象副本,默认是浅拷贝。wait()、notify()、notifyAll():用于线程间协作,必须在同步代码块或同步方法中调用。
2. == 与 equals 的区别 ⭐️⭐️⭐️
==:基本类型比较值;引用类型比较是否指向同一个对象。equals():默认与==一致;业务对象通常重写它来比较字段值。hashCode():用于HashMap、HashSet等哈希容器定位元素。
重写 equals() 时必须同时重写 hashCode(),并满足:
- 两个对象
equals()返回true,它们的hashCode()必须相同。 - 两个对象的
hashCode()相同,equals()不一定返回true。
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User user = (User) obj;
return id == user.id;
}
@Override
public int hashCode() {
return Integer.hashCode(id);
}
3. Record 是什么?
record 是 Java 14 引入、Java 16 正式发布的一种特殊类,专门用于表示不可变的数据对象(Data Carrier)。可以自动生成构造方法、访问器方法、equals()、hashCode() 和 toString() 等方法,从而减少样板代码。
11 String
1. String、StringBuilder、StringBuffer 的区别 ⭐⭐⭐
String:不可变,适合字符串常量和少量拼接。StringBuilder:可变、非线程安全,适合单线程频繁拼接,性能最好。StringBuffer:可变、线程安全,因同步开销性能较低,实际使用较少。
2. String 常用方法
常用方法包括:length()、equals()、substring()、replace()、trim()、isEmpty()。
12 java 8 新特性
1. java 8 你知道有什么新特性吗? ⭐⭐⭐
Java 8 主要引入了 Lambda 表达式、函数式接口、Stream API、接口默认方法、Optional、新的日期时间 API、方法引用和 CompletableFuture 等特性。
2. Lambda 表达式你了解吗 ⭐⭐
Lambda表达式是一种简洁的语法,本质是创建匿名内部类,主要用于简化函数式接口的使用(只有一个抽象方法的接口)。
(parameters) -> expression // 当lambda方法体只有一个表达式时使用,默认会返回表达式的结果
() -> {statements;} // 如果方法体有多条语句,需要用{}包裹,如果有返回值需要return
3. 什么是函数式接口?
函数式接口是只包含一个抽象方法的接口,可以使用 @FunctionalInterface 注解标记,例如 Runnable、Comparator、Predicate。
4. 什么是 Stream API?⭐⭐
Stream 是用于处理集合数据的 API,通过链式调用完成过滤、映射、排序和收集等操作。Stream 操作分为中间操作和终端操作:
- 中间操作:返回新的 Stream,并且具有惰性,例如
filter()、map()、sorted()。 - 终端操作:触发 Stream 执行并产生结果,例如
collect()、forEach()、count()。
List<String> result = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
5. 什么是 Optional?
Optional 是用于表示可能为空的容器类,可以减少部分空指针判断,但不能完全替代非空校验。
6. Java 8 的日期时间 API 有什么优势?
java.time 包提供了线程安全、不可变且设计更清晰的日期时间类,例如 LocalDate、LocalDateTime 和 ZonedDateTime,用于替代旧的 Date 和 Calendar。
7. completableFuture怎么用的?
CompletableFuture 是 Java 8 引入的异步编程工具类,用于异步编程,支持异步任务、链式调用、任务组合和异常处理。
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(String::toUpperCase)
.thenAccept(System.out::println);
13 java 21 新特性
1. java 21 有什么新特性?
Java 21 主要引入了虚拟线程、switch 模式匹配、Record 模式、有序集合等特性,其中虚拟线程最值得重点关注。
2. 什么是虚拟线程?⭐⭐⭐
虚拟线程是由 JVM 管理的轻量级线程(用户级别线程),相比平台线程占用资源更少,适合大量 I/O 阻塞任务,可以提高高并发场景下的吞吐量。
3. Java 21 对 switch 做了哪些增强?⭐⭐
Java 21 支持在 switch 中使用类型模式匹配和 when 守卫条件,可以根据对象的实际类型执行不同逻辑。
static String format(Object obj) {
return switch (obj) {
case Integer i -> "整数:" + i;
case String s when !s.isEmpty() -> "字符串:" + s;
default -> "其他类型";
};
}
4. 有序集合
Java 21 新增了 SequencedCollection、SequencedSet 和 SequencedMap 接口,统一提供获取首尾元素、反转集合等操作。
14 序列化
1. 怎么把一个对象从一个 jvm 转到另一个 jvm
先将对象序列化为双方约定的数据格式,再通过网络、RPC、消息队列或共享存储传输,接收方再进行反序列化;实际开发中通常使用 JSON、Protobuf 等跨语言格式。
2. 序列化和反序列化怎么实现呢?⭐⭐⭐
- 序列化:将对象转换为字节流或其他数据格式,便于存储和传输。
- 反序列化:将字节流或其他数据格式还原为对象。
Java 原生序列化可以通过 ObjectOutputStream 和 ObjectInputStream 实现,但存在跨语言能力差、性能较低和反序列化安全风险等问题,因此开发中通常使用 JSON、Protobuf 等序列化协议。
3. 将对象转换为二进制字节流具体怎么实现
Java 原生序列化要求对象实现 Serializable 接口:
ObjectOutputStream out =
new ObjectOutputStream(outputStream);
out.writeObject(object);
ObjectInputStream in =
new ObjectInputStream(inputStream);
Object object = in.readObject();
4. 序列化为什么要有版本号
serialVersionUID 用于标识可序列化类的版本,反序列化时 JVM 会根据它判断序列化数据与当前类是否兼容。
15 IO
1. 什么是 IO 流?⭐
Java IO 用于完成数据的输入和输出,按照数据方向(注意,是以内存为参考)分为输入流和输出流,按照处理单位分为字节流和字符流。
2. 字节流和字符流有什么区别?⭐⭐
- 字节流:以字节为单位处理数据,适合处理所有类型的文件,例如图片、视频和音频。
- 字符流:以字符为单位处理文本,并负责字符集的编解码,适合处理文本文件。
字节流的基类是 InputStream 和 OutputStream,字符流的基类是 Reader 和 Writer。
3. 什么情况下使用字节流,什么情况下使用字符流?⭐
处理图片、视频等二进制文件时使用字节流;处理文本文件时通常使用字符流,但如果需要严格控制编码,也可以使用字节流配合字符集进行转换。
4. 什么是缓冲流?⭐
缓冲流通过在内存中维护一个缓冲区,每次批量读取或写入数据,减少底层系统调用和磁盘 I/O 的次数,从而提高读写效率。例如 BufferedInputStream、BufferedOutputStream、BufferedReader 和 BufferedWriter。
5. 为什么要关闭 IO 流?⭐
IO 流通常会占用文件句柄、网络连接等系统资源,使用完后必须关闭,否则可能导致资源泄漏。推荐使用 try-with-resources 自动关闭资源。
6. try-with-resources 的原理是什么?⭐⭐
try-with-resources 会自动关闭实现了 AutoCloseable 接口的资源,即使代码执行过程中发生异常,也会自动调用资源的 close() 方法。
7. Java IO 和 NIO 有什么区别?⭐⭐
NIO(New IO)是 Java 1.4 引入的一套新的 IO API,它以 Buffer(缓冲区)、Channel(通道)和 Selector(选择器)为核心。相比传统 BIO 基于流且通常采用阻塞模型,NIO 支持非阻塞 IO,并可利用 Selector 实现一个线程管理多个连接,因此更适合高并发网络通信。像 Netty 等高性能网络框架底层就是基于 NIO 实现的。
8. flush() 和 close() 有什么区别?
flush() 用于将缓冲区中的数据写入目标设备,但不会释放资源;close() 会先刷新数据,再关闭流并释放相关资源。
9. 什么是 IO 流的包装?⭐⭐
Java IO 中,高级流可以包装基本流,在不改变基本流的情况下,为其增加缓冲、字符转换或读写基本数据类型等功能,这种设计体现了装饰器模式。
```java InputStream input = new FileInputStream("data.txt"); BufferedInputStream bufferedInput = new BufferedInputStream(input);