Java后端开发 笔试知识点复习(一)
某行某费 线上笔试
一、选择题和简答题
1. CompletableFuture是Java 8 引入的异步编程工具,用于异步任务的组合和链式调用。
通过静态工厂方法可以创建CompletableFuture实例:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
支持链式调用,可以将多个异步任务串联起来:
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println);
多个 CompletableFuture 可以组合成一个新的 CompletableFuture:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2)
.thenAccept(System.out::println);
支持多种回调函数,如 thenApply、thenAccept、thenRun:
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println)
.thenRun(() -> System.out.println("Done"));
2. 释放线程持有的锁——wait()
java中有关线程的操作:
run(): 线程执行的入口方法。run()是Runnable接口或Thread类中定义的核心方法,包含线程实际执行的代码逻辑。直接调用run()不会启动新线程,而是在当前线程中同步执行。 是否释放锁取决于其内部是否调用了wait()或同步代码块的执行结束。sleep(): 调用sleep()不会释放任何锁,线程即使休眠仍持有锁。其他线程无法进入该对象的同步代码块或方法。Thread.sleep(long millis)使当前线程暂停执行指定毫秒数,可能抛出InterruptedException,通常用于模拟耗时操作或控制执行节奏。sleep()属于Thread类。wait():wait()是Object类的方法,用于线程间通信。调用前必须持有对象锁(在synchronized块内),会释放锁并让线程进入等待状态,直到其他线程调用notify()/notifyAll()唤醒它。这是唯一明确释放锁的操作。- start():启动线程,调用后线程进入就绪状态,等待CPU调度。
- join():等待线程执行完成,主线程会阻塞直到目标线程结束。
- interrupt():中断线程,设置线程的中断标志位。
- yield():让出CPU资源,当前线程从运行状态转为就绪状态。
- notify() / notifyAll():唤醒在对象上等待的单个或所有线程。
线程状态管理——线程的生命周期中涉及多种状态转换,相关操作包括:
- isAlive():检查线程是否存活。
- getState():获取线程当前状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)。
- setDaemon(boolean):设置线程为守护线程(后台线程)。
线程同步与锁——多线程并发操作时常用的同步机制:
- synchronized:关键字,用于方法或代码块同步。
- Lock接口:显式锁,提供更灵活的锁控制(如ReentrantLock)。
- wait() / notify():配合synchronized使用,实现线程间通信。
线程池操作——Java并发包(java.util.concurrent)提供了线程池相关操作:
- ExecutorService:线程池接口,常用实现类如ThreadPoolExecutor。
- submit() / execute():提交任务到线程池。
- shutdown() / shutdownNow():关闭线程池。
其他高级操作
- ThreadLocal:线程局部变量,每个线程独立存储数据。
- Future / Callable:获取线程执行结果。
- ForkJoinPool:分治任务的线程池框架。
Java中创建线程
- 继承Thread类
- 实现Runnable接口
3. HashMap和HashTable
| 比较点 | HashMap | Hashtable |
|---|---|---|
| 出现时间 | JDK 1.2 引入,属于新集合框架 | JDK 1.0 就存在的遗留类 (Legacy) |
| 线程安全 | 非线程安全 | 线程安全 (方法使用 synchronized 修饰) |
| 性能 | 高(无锁竞争) | 低(锁竞争严重) |
| 允许 null | 允许一个 null 键,多个 null 值 | 不允许 null 键和 null 值(会抛 NPE) |
| 继承体系 | 继承 AbstractMap,实现 Map 接口 | 继承 Dictionary,实现 Map 接口 |
| 迭代器 | Iterator (fail-fast) | 除了 Iterator,还有 Enumeration (非 fail-fast) |
| 初始容量 | 默认 16 | 默认 11 |
| 扩容机制 | 容量变为原来的 2 倍 | 容量变为原来的 2 倍 + 1 |
| hash 计算 | 对 key 的 hashCode 进行扰动处理 | 直接使用 key 的 hashCode |
是否支持 contains 方法 |
有 containsKey 和 containsValue |
有 contains 方法(类似 containsValue) |
4. 设计模式
创建型模式:关注对象的创建过程,将对象的创建与使用分离。
- 工厂方法:定义一个创建对象的接口,由子类决定实例化哪个类。
- 抽象工厂:提供一组相关或依赖对象的创建接口,无需指定具体类。
- 单例:确保一个类仅有一个实例,并提供全局访问点。
- 建造者:将一个复杂对象的构建与其表示分离,支持分步骤构造。
- 原型:通过复制现有对象来创建新对象,避免重复初始化。
结构型模式:关注类和对象的组合,通过继承或组合构建更大的结构。
- 适配器:将一个类的接口转换成客户端期望的另一种接口。
- 桥接:将抽象部分与实现部分分离,使两者可独立变化。
- 组合:将对象组织成树形结构以表示“部分-整体”层次关系。
- 装饰器:动态地为对象添加额外职责,比继承更灵活。
- 外观:为复杂子系统提供统一的简化接口。
- 享元:运用共享技术有效支持大量细粒度对象。
- 代理:为其他对象提供一种代理以控制对这个对象的访问。
行为型模式:关注对象之间的交互与职责分配。
- 责任链:将请求的发送者和接收者解耦,允许多个对象处理请求。
- 命令:将请求封装为对象,支持参数化、队列或日志请求。
- 解释器:定义语言的文法表示,并提供解释器处理该语言。
- 迭代器:提供一种方法顺序访问聚合对象的元素而不暴露内部表示。
- 中介者:用一个中介对象封装一系列对象交互,降低耦合度。
- 备忘录:捕获对象内部状态并在不破坏封装情况下恢复该状态。
- 观察者:定义对象间一对多依赖关系,使得状态变化时自动通知。
- 状态:允许对象在内部状态改变时改变其行为。
- 策略:定义一系列算法并使其可互换,独立于使用它的客户端。
- 模板方法:在父类中定义算法框架,允许子类重写特定步骤。
- 访问者:在不修改元素类的前提下为其添加新的操作。
5. TCP连接建立与释放
三次握手(建立连接)
客户端 (Client) 服务器 (Server)
CLOSED LISTEN
| |
| SYN=1, seq=x (不能携带数据) |
| ----------------------------------------> |
| | 收到 SYN
| | 状态变为 SYN-RCVD
| SYN=1, ACK=1, seq=y, ack=x+1 |
| <---------------------------------------- |
| |
状态变为 ESTABLISHED |
| |
| ACK=1, seq=x+1, ack=y+1 (可携带数据) |
| ----------------------------------------> |
| | 状态变为 ESTABLISHED
| |
四次挥手(释放连接,客户端主动关闭)
主动关闭方 (Client) 被动关闭方 (Server)
ESTABLISHED ESTABLISHED
| |
| FIN=1, seq=u (可能携带最后数据) |
| ----------------------------------------> |
状态变为 FIN-WAIT-1 | 收到 FIN,回复 ACK
| | 状态变为 CLOSE-WAIT
| ACK=1, seq=v, ack=u+1 |
| <---------------------------------------- |
状态变为 FIN-WAIT-2 |
| | (服务器可能还有数据要发)
| | ... 数据传输 ...
| |
| FIN=1, ACK=1, seq=w, ack=u+1 |
| <---------------------------------------- |
| 状态变为 LAST-ACK
状态变为 TIME-WAIT |
| |
| ACK=1, seq=u+1, ack=w+1 |
| ----------------------------------------> |
| 状态变为 CLOSED
等待 2MSL 后关闭
状态变为 CLOSED
6. volatile作用
-
保证可见性:线程修改volatile变量后,会强制写回主内存,其他线程读取时从主内存刷新。
-
禁止指令重排:通过内存屏障防止编译器和CPU重排序。
-
不保证原子性:例如
count++操作仍需要加锁。
7. equals()和==
-
==:比较基本数据类型时比值,比较引用类型时比内存地址。
-
equals:基本数据类型没有equals,引用类型同==比内存地址;但很多类(如String、Integer)重写了equals,用于比较内容。
8. 触发Full GC
Full GC 是指对整个堆(新生代、老年代、方法区/元空间)进行垃圾回收,通常伴随较长的停顿。触发场景包括:
-
老年代空间不足
-
对象晋升失败:Minor GC 后存活对象需要进入老年代,但老年代剩余空间不足。
-
大对象直接分配:大对象超过阈值直接进入老年代,若空间不足则触发 Full GC。
-
CMS 并发模式失败:CMS 回收期间老年代被快速填满,退化为 Full GC。
-
-
永久代/元空间不足
-
加载的类、方法、常量池过多,导致方法区溢出。
-
-
显式调用 System.gc()
-
代码中调用
System.gc()建议 JVM 执行 Full GC(可通过-XX:+DisableExplicitGC屏蔽)。
-
-
GC 算法相关
-
G1 回收失败:混合回收无法满足内存需求。
-
空间分配担保失败:Minor GC 前检查老年代最大连续空间不足,且担保不允许冒险。
-
-
内存泄漏:对象持续无法回收,堆逐渐占满。
9. 多态
多态是面向对象三大特性之一,指同一操作作用于不同对象,可以有不同的解释和执行结果。简单说:父类引用指向子类对象,调用方法时执行子类的实现。提高代码的扩展性和可维护性,减少耦合。
-
核心:类型兼容(子类对象可以赋值给父类类型的变量)+动态绑定(程序运行时,JVM 会根据变量指向的实际对象类型,而非变量的声明类型,来决定调用哪个方法),编译看左边(声明类型),运行看右边(实际对象)
-
表现形式:
-
编译时多态:方法重载(静态绑定)。同一类中,方法名相同,参数列表(个数/类型/顺序)不同。编译时就确定调用哪个方法。
-
运行时多态:方法重写Override(动态绑定)。有继承关系,子类重写父类的非private、非final、非static方法,父类引用指向子类对象。
-
向上转型:子类→父类,自动完成,目的是 “统一类型”;
-
向下转型:父类→子类,强制转换,目的是 “调用子类特有方法”;注意:向下转型前必须用
instanceof判断,否则会抛出ClassCastException(类型转换异常)。
-
-
面向对象三大特性:
- 封装:隐藏内部细节,通过接口访问,核心是 “保护和隐藏”;
- 继承:复用父类代码,扩展子类功能,核心是 “复用和扩展”;
- 多态:父类引用指向子类对象,同一行为不同表现,核心是 “灵活和扩展”。
这三个特性相互配合:封装保证数据安全,继承实现代码复用,多态提升代码灵活性,共同构成
10. 线程池参数及作用
-
corePoolSize:核心线程数,即使空闲也会保留(除非设置 allowCoreThreadTimeOut)。
-
maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。
-
keepAliveTime:非核心线程空闲存活时间。
-
unit:时间单位。
-
workQueue:任务队列,用于存放等待执行的任务。
-
threadFactory:线程工厂,用于创建新线程。
-
handler:拒绝策略,当任务无法执行时的处理方式。
corePoolSize和maximumPoolSize的大小相比:通常 maximumPoolSize >= corePoolSize
-
当提交任务时,如果当前线程数 < corePoolSize,则创建新线程执行任务。
-
如果当前线程数 >= corePoolSize,则将任务放入队列。
-
如果队列已满,且当前线程数 < maximumPoolSize,则创建新线程(非核心)执行任务。
-
如果当前线程数 >= maximumPoolSize 且队列已满,则执行拒绝策略。
11. 反射:允许程序在运行时获取类的信息(属性、方法、构造器、注解等)并操作类或对象的属性和方法。
只能用子系统类,不能用自定义类?错误
-
反射可以操作任何类:包括 JDK 自带类、第三方库类以及自定义类。只要类在类路径中,就可以通过
Class.forName()或.class获取 Class 对象。 -
限制:某些情况下可能受限于模块化(Java 9+),如果自定义类不在当前模块或未开放包,则可能无法访问私有成员。但这是模块化权限控制,并非反射本身限制。
核心用途:框架 / 工具类场景优先用,业务代码尽量少用(避免性能和封装性问题)。
-
框架开发:几乎所有主流 Java 框架(Spring、MyBatis、Hibernate、JUnit 等)都依赖反射实现 “解耦” 和 “动态化”。框架无法在编译期知道你自定义的类(比如
UserService、OrderMapper),通过反射可以在运行时加载并实例化这些类,调用指定方法。 示例:Spring 的 IOC 容器会读取配置文件(或注解),通过反射创建对象并管理依赖;MyBatis 通过反射将数据库查询结果映射到实体类的私有属性,无需手动赋值。 -
动态调用方法/修改私有属性:反射可以突破访问修饰符的限制(比如调用
private方法、修改private属性) -
实现通用化工具类:反射可以编写 “通用型” 代码,适配不同类的操作,避免重复编写相似逻辑。例如BeanUtils.copyProperties,实现任意对象的属性拷贝。
-
动态加载类(突破编译期依赖):反射可以在运行时根据条件加载不同的类,适用于 “插件化开发” 或 “动态扩展” 场景。
-
处理注解(注解的核心配套机制):Java 注解本身不会生效,必须通过反射读取注解信息并执行逻辑(比如 Spring 的
@Controller、@Autowired,JUnit 的@Test)。
12. Java中Stream API
-
Stream 不是数据结构:它不存储数据,而是对数据源(集合、数组等)进行函数式操作的工具。
-
操作分类:
-
中间操作:返回新的 Stream,可链式调用,如
filter、map、sorted。惰性求值,只有遇到终端操作才执行。 -
终端操作:触发实际计算,返回结果或副作用,如
forEach、collect、reduce。
-
-
串行流与并行流:
-
串行流:单线程处理,通过
stream()获取。 -
并行流:多线程处理,通过
parallelStream()或parallel()转换,利用 Fork/Join 框架。
-
-
特点:支持函数式编程,提高代码可读性,但需注意线程安全。
13. 数据库连接池的优点
数据库连接池是一种管理数据库连接的技术。
主要优点:
-
减少连接创建开销:预先创建并维护一定数量的连接,避免每次请求都建立和关闭连接(耗时资源)。解决 “频繁创建 / 销毁数据库连接” 性能损耗。
-
资源复用:有效控制连接数量,防止过多连接耗尽数据库资源。
-
统一管理:可配置连接超时、泄漏检测、统计信息等。
-
提高响应速度:直接从池中获取连接,无需等待 TCP 握手等过程。
-
提高系统可靠性:连接池能自动回收无效连接,避免内存泄漏。
常见连接池:HikariCP(目前最优,高性能、轻量级,Spring Boot 2.x 及以上默认内置的连接池)、Druid(阿里开源,功能最全,监控、安全)、.Tomcat JDBC Pool(轻量级,Tomcat 内置)、C3P0 (老牌,逐渐淘汰)等。
连接池参数和特性:
- 连接池大小配置:
- 最大连接数(根据数据库性能和业务并发量调整)、
- 最小空闲连接数(保持一定的空闲连接,避免高峰期频繁创建连接,建议设为最大连接数的 1/4~1/2)、
- 初始化连接数(项目启动时创建的连接数,建议等于最小空闲连接数,减少启动后首次请求的耗时)
- 超时配置:
- 连接超时(获取连接的最大等待时间,超时抛出异常(建议 3~5 秒),避免请求无限阻塞)、
- 空闲超时(空闲连接的最大存活时间,超时自动回收(建议 10~30 分钟),避免连接长期闲置占用资源)、
- 连接存活时间(连接的最大生命周期(建议 1~2 小时),避免数据库端主动断开连接导致的 “无效连接”)
- 性能特性:
- 优先选择无锁 / 轻量级锁的连接池(如 HikariCP),减少并发竞争;
- 支持连接池预热(启动时创建所有最小空闲连接),避免首次请求慢。
14. 通过反射调用私有方法需先执行setAccessible(true) 来取消 Java 语言访问检查。
私有方法默认不可访问,setAccessible(true) 可以强制访问。(注意:setAccessible 会破坏封装,需谨慎使用,且在模块化系统中可能受模块限制)
-
步骤:
-
获取 Class 对象。
-
通过
getDeclaredMethod("methodName", parameterTypes)获取 Method 对象(包括私有)。 -
调用
method.setAccessible(true)。 -
调用
method.invoke(instance, args)。
-
15. Exception分类
Java 中的异常体系以 Throwable 为根,分为两大类:
-
Error:系统级错误,通常由 JVM 抛出,程序无法处理,如
OutOfMemoryError、StackOverflowError。 -
Exception:程序可处理的异常,又分为:
-
受检异常(Checked Exception):编译时强制处理(try-catch 或 throws),如
IOException、SQLException。 -
非受检异常(Unchecked Exception / RuntimeException):运行时异常,可不用显式处理,如
NullPointerException、IllegalArgumentException。
-
16. 数据库的隔离机制,脏读,不可重复读,幻读,并结合具体事例说明这三种现象如何产生。
数据库事务的隔离级别定义了并发事务之间的可见性。四种隔离级别(从低到高):
(1)读未提交(Read Uncommitted):允许脏读。
(2)读已提交(Read Committed):避免脏读,但可能发生不可重复读。
(3)可重复读(Repeatable Read):避免脏读和不可重复读,但可能发生幻读(MySQL InnoDB 通过间隙锁解决)。
(4)串行化(Serializable):最高级别,避免所有问题,但并发性能低。
并发问题:
-
脏读:一个事务读取了另一个事务未提交的数据。
-
例:事务A将余额从100改为200(未提交),事务B读取到200,随后事务A回滚,余额仍是100,但事务B基于200做了错误操作。
-
-
不可重复读:一个事务内两次读取同一数据,得到不同结果(因为另一事务修改并提交了该数据)。
-
例:事务A第一次读取余额为100,事务B修改为200并提交,事务A再次读取得到200,两次读取不一致。
-
-
幻读:一个事务内两次查询同一范围的数据,得到不同行数(因为另一事务插入或删除了数据)。
-
例:事务A查询所有余额>100的用户,得到3条;事务B插入一条余额150的用户并提交;事务A再次查询得到4条,出现幻行。
-
注意:不可重复读针对同一条记录内容变化,幻读针对记录数量变化。
二、编程题
17. 输入s,找到s中最长的回文子串(反序与原序相同)
思路:回文串的中心有两种情况:
-
奇数长度:中心是一个字符,例如
"aba"的中心是'b'。 -
偶数长度:中心是两个字符之间,例如
"abba"的中心是"bb"之间。
我们遍历字符串的每个位置,将其作为奇数中心,并向两边扩展;同时将每个位置作为偶数中心的左半部分(即与下一个字符一起作为中心),向两边扩展。记录下能扩展的最大长度,并更新最长回文的起始和结束位置。
步骤:
(1)初始化 start = 0, end = 0,用于记录最长回文的起始和结束下标。
(2)遍历字符串的每个索引 i:
- 以
i为中心向两边扩展(奇数情况),得到长度len1。 - 以
i和i+1为中心向两边扩展(偶数情况),得到长度len2。 - 取
len = max(len1, len2)。 - 如果
len > end - start + 1,则更新start和end:-
end = i + len / 2 -
start = i - (len - 1) / 2
-
(3)返回 s.substring(start, end + 1)。
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) return "";
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i); // 奇数中心
int len2 = expandAroundCenter(s, i, i + 1); // 偶数中心
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
private int expandAroundCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
// 当循环结束时,left 和 right 指向不匹配或边界外,回文长度为 right - left - 1
return right - left - 1;
}
18. 找出给定字符中出现次数最多的字符及其出现次数
解法:使用哈希表统计频率,然后遍历找最大值。
public char mostFrequentChar(String s) {
if (s == null || s.isEmpty()) throw new IllegalArgumentException();
int[] count = new int[256]; // 假设 ASCII
for (char c : s.toCharArray()) {
count[c]++;
}
char maxChar = s.charAt(0);
int maxCount = 0;
for (char c : s.toCharArray()) {
if (count[c] > maxCount) {
maxCount = count[c];
maxChar = c;
}
}
// 或者遍历 count 数组
return maxChar;
}
// 返回次数可另外获取
19. 给定一个整数数组nums,找到一个具有最大连续和的连续数组
解法:经典动态规划(Kadane算法)。遍历数组,维护当前连续和 cur 和最大和 max,若 cur 为负则重置为当前元素。
public int maxSubArray(int[] nums) {
int max = nums[0];
int cur = nums[0];
for (int i = 1; i < nums.length; i++) {
cur = Math.max(nums[i], cur + nums[i]);
max = Math.max(max, cur);
}
return max;
}
// 若需返回子数组,需记录起始和结束索引
更多推荐




所有评论(0)