Java基础常见面试题总结(下)
Exception 和 Error 有什么区别?
从字节码角度分析try catch finally
这个语法糖背后的实现原理从字节码角度分析一下try-catch-finally执行流程客
java反射机制
03、反射:获取类的构造器的作用_哔哩哔哩_bilibili
java SPI机制
Java 中的 SPI 机制就是在每次类加载的时候会先去找到 class 相对目录下的 META-INF
文件夹下的 services 文件夹下的文件,将这个文件夹下面的所有文件先加载到内存中,然后根据这些文件的文件名和里面的文件内容找到相应接口的具体实现类,找到实现类后就可以通过反射去生成对应的对象,保存在一个 list 列表里面,所以可以通过迭代或者遍历的方式拿到对应的实例对象,生成不同的实现。
为什么 I/O 流操作要分为字节流操作和字符流操作呢?
- 字节流适用于任何场景,而且有字节缓冲流,能提高读取和输出的效率------BufferedOutPutStream和BufferedInputStream.
- 字符流是为了对应汉字出现的情况,在GBK中汉字占2个字节,在UTF-8中汉字占3个字节,所以我们通过字节流读取文件的时候按照逐个字节转换就会导致乱码。所以出现了字符流。
- 为什么使用字节流复制粘贴中文文件不会乱码的原因:不论GBK编码还是UTF-8编码,中文的第一个字节都是负数,逐个字节读取文件后会根据不同的编码自动进行拼接。而我们手动向文件输入数据后,使用write("中国".getBytes()),其实getBytes默认是UTF-8编码,如果文件是UTF-8编码自然不会乱码。
常用字符编码所占字节数?
UTF-8:英文占1B,中文占3B。 UNICODE:所有字符都占2B。 GBK:英文占1B,中文占2B。
字节缓冲流
字节缓冲流这里采用了装饰器模式来增强 InputStream
和OutputStream
子类对象的功能。 我们可以通过BufferedInputStream(字节缓冲输入流)来增强FileInputStream。
java
// 新建一个 BufferedInputStream 对象
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("input.txt"));
字节缓冲流会先将读取到的字节存放在缓存区,大幅减少 IO 次数,提高读取效率。
BufferedInputStream
内部维护了一个缓冲区,这个缓冲区实际就是一个字节数组.
字符缓冲流
BufferedReader
(字符缓冲输入流)和 BufferedWriter
(字符缓冲输出流)类似于 BufferedInputStream
(字节缓冲输入流)和BufferedOutputStream
(字节缓冲输入流),内部都维护了一个字节数组作为缓冲区。
随机访问流
RandomAccessFile
比较常见的一个应用就是实现大文件的 断点续传 。何谓断点续传?简单来说就是上传文件中途暂停或失败(比如遇到网络问题)之后,不需要重新上传,只需要上传那些未成功上传的文件分片即可。分片(先将文件切分成多个文件分片)上传是断点续传的基础。
java
public boolean merge(String fileName) throws IOException {
byte[] buffer = new byte[1024 * 10]; // 创建一个10KB的缓冲区
int len = -1; // 存储读取的字节数
try (RandomAccessFile oSavedFile = new RandomAccessFile(fileName, "rw")) { // 创建一个用于存储合并数据的文件
for (int i = 0; i < DOWNLOAD_THREAD_NUM; i++) { // 循环遍历多个临时文件
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(fileName + FILE_TEMP_SUFFIX + i))) { // 为每个临时文件创建输入流
while ((len = bis.read(buffer)) != -1) { // 从临时文件读取数据直到文件末尾
oSavedFile.write(buffer, 0, len); // 将读取的数据写入合并文件中
}
}
}
LogUtils.info("文件合并完毕{}", fileName); // 合并完成后输出日志
} catch (Exception e) { // 捕获可能出现的异常
e.printStackTrace(); // 打印异常信息
return false; // 返回合并失败
}
return true; // 返回合并成功
}
Java IO 设计模式总结
装饰器模式
装饰器模式通过组合替代继承来扩展原始类的功能,在一些继承关系比较复杂的场景(IO 这一场景各种类的继承关系就比较复杂)更加实用。 对于字节流来说, FilterInputStream
(对应输入流)和FilterOutputStream
(对应输出流)是装饰器模式的核心,分别用于增强 InputStream
和OutputStream
子类对象的功能。
BufferedInputStream
构造函数如下:
java
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
适配器模式
工厂模式
观察者模式
Java 中 3 种常见 IO 模型
Java 中有哪些常见的语法糖?
Java 中的语法糖是指一些编译器提供的语法简化,使得代码更易读、更简洁,但实际上编译器会将这些语法糖转换成标准的 Java 代码。以下是一些常见的 Java 语法糖:
-
自动装箱与拆箱(Autoboxing and Unboxing) : 允许基本数据类型和对应的包装类之间自动转换,例如
int
和Integer
、double
和Double
之间的转换。这使得在需要使用包装类的地方可以直接使用基本数据类型,编译器会自动进行转换。 -
增强的 for 循环(Enhanced for Loop) : 使用简单的语法来遍历数组或集合,而不需要使用传统的索引迭代方式。例如:
for (String item : items) { /* ... */ }
-
可变参数(Varargs) : 允许在方法中传递数量可变的参数,而不需要显式地创建数组。例如:
public void foo(int... args) { /* ... */ }
-
Diamond 语法(Diamond Operator) : 在 Java 7 中引入,允许在实例化泛型类时省略右侧的泛型类型声明。例如:
List<String> list = new ArrayList<>();
-
静态导入(Static Import) : 允许在不指定类名的情况下直接访问静态成员。例如:
import static java.lang.Math.*;
允许直接使用PI
、sqrt
等静态成员,而不需要Math.PI
、Math.sqrt
。 -
字符串连接优化(String Concatenation Optimization) : 使用加号
+
进行字符串连接时,编译器会优化成StringBuilder
的append
操作,提高字符串拼接的效率。 -
try-with-resources 语句(自动资源管理) : 简化了对需要显式关闭的资源(比如流)的处理,在
try
后面的括号内声明资源,程序执行完毕后会自动关闭这些资源。例如:javatry (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) { // 使用 BufferedReader 读取文件内容 } catch (IOException e) { // 异常处理 }
这些语法糖使得 Java 编程更加方便和简洁,但实际上它们在编译时会被转换成对应的标准 Java 代码,保持了 Java 的向后兼容性。
- Lambda表达式 没错,Lambda 表达式是 Java 中引入的一种函数式编程的语法糖。它提供了一种更简洁、更便利地表示匿名函数的方式,可以作为方法参数传递,或者用来简化某些代码块的书写。
Lambda 表达式的基本结构:
Lambda 表达式由三个部分组成:参数列表、箭头符号 ->
和函数体。
java
(parameter_list) -> { lambda_body }
- 参数列表:类似于方法参数列表,但不需要指定参数的类型,Java 编译器会根据上下文推断参数类型。
- 箭头符号
->
:用于分隔参数列表和 Lambda 表达式的主体。 - Lambda 主体:包含了 Lambda 表达式执行的代码块,可以是一行简单的表达式,也可以是多行代码块。
Lambda 表达式的特性:
-
简洁性:Lambda 表达式可以大大简化匿名函数的书写,尤其是针对函数式接口的实现。
-
函数式编程支持:Lambda 表达式支持函数式编程风格,可以更轻松地进行函数作为参数传递和使用。
-
闭包性:Lambda 表达式可以访问外部作用域的变量或常量,这些变量或常量可以是 final 或 effectively final(在Lambda表达式内部不被修改)。
Lambda 表达式的应用场景:
-
集合操作 :对集合进行过滤、映射、排序等操作时,Lambda 表达式可以非常方便地传递给集合的方法,如
filter()
、map()
、forEach()
等。 -
多线程:在多线程编程中,可以使用 Lambda 表达式来简化线程的创建和执行。
-
事件处理:在图形用户界面(GUI)编程中,Lambda 表达式可以简化事件监听器的设置。
-
函数式接口的实现:Lambda 表达式通常与函数式接口(只包含一个抽象方法的接口)一起使用,实现接口的匿名实例。
Lambda 表达式的引入让 Java 更加灵活和适应函数式编程范式,使得代码更为简洁、可读性更高,并且提供了更好的支持用于处理集合、多线程和事件等场景。
举例,
java
import java.util.ArrayList;
import java.util.List;
public class LambdaExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
// 使用 Lambda 表达式筛选偶数并输出
numbers.stream()
.filter(number -> number % 2 == 0) // 筛选偶数
.forEach(System.out::println); // 输出偶数
}
}
//输出:
2
4