Java语言具备多个关键特性,使其在企业级开发中广泛应用:
| 对比维度 | 基本数据类型(如int、float) | 包装类(如Integer、Float) |
|---|---|---|
| 本质 | 原始值类型,不具有对象属性 | 引用类型,直接或间接继承自Object类 |
| 默认值 | 存在明确初始值(例如int为0,boolean为false) | 默认为null,表示未初始化状态 |
| 使用场景 | 适用于局部变量、简单运算等高性能需求场合 | 用于集合类(如List)、泛型操作或需要表达“无值”语义的情境 |
| 缓存机制 | 无缓存功能 | 部分类型拥有常量池优化,如Integer[-128~127]、Byte、Short等范围内的值会被复用 |
关键知识点说明:
int i = new Integer(10)Integer j = 10==比较两个位于[-128,127]区间内的Integer对象时返回true,因为它们共享同一缓存实例。Integer a = 127; Integer b = 127;a == b==比较结果为false,此时应使用equals()方法进行内容比对。Integer c = 128; Integer d = 128;c == dequals()来比较包装类的值是否相等。equals()主要差异体现在可变性与线程安全性两个方面:
底层原理补充:
private final char value[]常见误区提醒:
final int[] arr = {1,2,3}; arr[0] = 4;arr = new int[5];| 比较维度 | 接口(Interface) | 抽象类(Abstract Class) |
|---|---|---|
| 继承方式 | 支持多实现,一个类可同时实现多个接口 | 仅支持单继承,每个类最多只能继承一个抽象类 |
| 成员变量 | 只能定义public static final类型的常量 | 可包含普通成员变量、静态变量及常量 |
| 成员方法 | JDK8前仅允许抽象方法;JDK8起支持default和static方法;JDK9+还允许private方法 | 可包含抽象方法、具体实现方法以及静态方法 |
| 构造方法 | 不存在构造器 | 可以定义构造方法,虽不能直接实例化,但可供子类调用以完成初始化 |
| 设计目的 | 侧重于定义行为契约,促进解耦,例如List接口规定集合操作规范 | 强调共性抽取,作为模板复用代码,例如HttpServlet封装通用请求处理流程 |
典型应用场景:
RunnableAbstractListJava的异常体系以Throwable为顶层父类,其下分为两大分支:
Throwable
ErrorException
RuntimeException常用异常处理关键字:
trycatchfinallythrowthrow new IllegalArgumentException("参数非法")throws答案:
Java集合框架主要分为两大体系,所有核心类均位于java.util包中。
单列集合(Collection):用于存储单一元素,其核心子接口包括:
双列集合(Map):用于存储键值对(key-value),主要实现类包括HashMap、TreeMap、LinkedHashMap以及ConcurrentHashMap。
关键特性说明:
get(index)方法获取指定位置的元素;Listget(int index)hashCode()和equals()方法来确保元素的唯一性;Setequals()hashCode()forEach()、computeIfAbsent()等便捷操作方法。MapforEach()computeIfAbsent()java.utilCollectionListSetMap
答案:
| 比较维度 | ArrayList(基于动态数组) | LinkedList(基于双向链表) |
|---|---|---|
| 底层结构 | Object[] 数组 | 每个节点包含prev、next和value的双向链表 |
| 访问效率 | 随机访问快(O(1)),可通过索引直接定位 | 随机访问慢(O(n)),需逐个遍历查找 |
| 增删效率 | 尾部增删较快(O(1)),中间位置增删较慢(需移动后续元素,O(n)) | 任意位置增删快(O(1),仅修改指针),但尾部操作仍需遍历至末尾(O(n),可通过last引用优化) |
| 内存占用 | 连续内存空间,开销小(无额外指针) | 非连续内存,每个节点有prev和next两个指针,内存开销较大 |
| 线程安全 | 不安全 | 不安全 |
应用场景建议:
ThrowableAutoCloseable
答案:
HashMap是基于哈希表的Map实现,采用“数组 + 链表/红黑树”的结构设计,旨在兼顾查询与修改性能。
JDK1.7 实现方式:
hashCode()方法获取哈希码;hashCode() ^ (hashCode() >>> 16)(n - 1) & hash计算出数组下标(n为数组长度);JDK1.8 主要优化点:
容量 × 负载因子时触发扩容,新容量为原容量的两倍;哈希冲突解决方案:
线程安全性说明:
HashMap本身非线程安全,在多线程环境下可能出现:
推荐替代方案:ConcurrentHashMap 或 Collections.synchronizedMap()。
ConcurrentHashMapCollections.synchronizedMap(new HashMap<>())
答案:
ConcurrentHashMap是HashMap的线程安全版本,其核心改进在于锁策略的演进。
JDK1.7 实现机制:
JDK1.8 重大重构:
computeIfAbsent()、forEach()等原子性操作,整体性能优于JDK1.7版本。computeIfAbsent()forEach()答案:
HashSet内部基于HashMap实现,本质上是一个Key-only的Map结构。
综上,HashSet是利用哈希表实现的Set接口典型实现,兼顾高效存取与去重能力。
HashSet 的底层实现基于 HashMap,其核心原理如下:
在创建 HashSet 时,其构造方法会内部初始化一个 HashMap 实例。当进行元素添加操作时:
add(E e)
put(e, PRESENT)
PRESENT
元素的唯一性由 HashMap 中 key 的不可重复特性来保证,该机制依赖于两个关键方法的协同工作:
equals()
hashCode()
HashSet 具备以下特性:无序性、元素不可重复、非线程安全。在没有哈希冲突的情况下,增删查操作的时间复杂度为 O(1)。
当将自定义对象作为 HashSet 的元素时,必须重写以下两个方法以确保元素唯一性的正确判断:
equals()
hashCode()
若未重写这两个方法,则默认使用 Object 类提供的实现,即比较对象的内存地址,这通常无法满足业务场景下的逻辑相等需求。
重写规范如下:
equals 方法返回 true,则它们的 hashCode 值必须相同;equals()
hashCode()
hashCode 相同(哈希冲突),equals 方法不一定返回 true。方式一:继承 Thread 类
通过继承 Thread 类并重写其 run() 方法来定义线程执行体,随后调用 start() 方法启动线程。该方法底层通过 native 的 JNI 调用创建操作系统级别的线程。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
// 启动线程
new MyThread().start();
Thread
run()
start()
start0()
方式二:实现 Runnable 接口
实现 Runnable 接口并重写 run() 方法,然后将该实例传递给 Thread 构造函数进行启动。这种方式避免了单继承限制,更利于资源共享。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}
// 启动线程
new Thread(new MyRunnable()).start();
Runnable
run()
Thread
方式三:实现 Callable 接口
Callable 接口允许线程任务有返回值,并能抛出异常。需配合 FutureTask 使用,最终仍通过 Thread 启动。
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(); // 阻塞等待结果返回
Callable
call()
FutureTask
Thread
对比总结:
Java 中线程的状态由 Thread.State 枚举定义,共包含六种状态:
Thread.State
NEW
start()
RUNNABLE
BLOCKED
synchronized
WAITING
Object.wait()
Thread.join()
LockSupport.park()
TIMED_WAITING
Thread.sleep(ms)
Object.wait(ms)
Thread.join(ms)
TERMINATED
核心状态转换路径:
NEW
RUNNABLE
start()
TERMINATED
RUNNABLE
BLOCKED
RUNNABLE
WAITING/TIMED_WAITING
RUNNABLE
| 对比维度 | synchronized(内置锁) | Lock(显式锁,如 ReentrantLock) |
|---|---|---|
| 锁的实现层级 | JVM 层面实现(基于 C++) | JDK 层面实现(纯 Java 编写) |
| 获取与释放方式 | 进入同步代码块自动获取,退出或异常时自动释放 | 需手动调用 lock() 获取,unlock() 释放(建议在 finally 块中执行) |
| 锁类型支持 | 可重入、非公平锁(默认),JDK 6+ 引入偏向锁、轻量级锁、重量级锁升级机制 | 可重入,构造时可指定公平或非公平模式 |
| 功能扩展性 | 功能较基础,仅提供基本同步控制 | 支持中断响应、超时获取锁、多个条件变量等高级特性 |
4. volatile关键字的作用?
volatile 是Java中提供的一种轻量级同步机制,主要具备两个核心功能:
局限性说明:
volatile boolean flag = false;
经典应用场景 —— 双重校验锁(DCL)单例模式:
public class Singleton {
// 使用volatile确保instance的写操作不会发生指令重排
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查:无锁快速返回
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查:防止并发创建多个实例
instance = new Singleton();
// volatile保证以下三步不会重排序:
// 1. 分配内存空间
// 2. 初始化对象
// 3. instance指向内存地址
}
}
}
return instance;
}
}
volatile int i = 0; i++;
5. 线程池的核心参数与工作流程?
Java中的线程池由 ThreadPoolExecutor 类实现,基于“池化技术”管理线程资源,有效降低频繁创建和销毁线程带来的系统开销,提升高并发下的执行效率。
ThreadPoolExecutor
核心构造参数如下:
| 参数名 | 说明 |
| corePoolSize | 核心线程数量,即使空闲也不会被回收(除非设置allowCoreThreadTimeOut) |
| maximumPoolSize | 最大线程总数,包括核心线程与临时线程 |
| keepAliveTime | 超出核心线程数的临时线程,在空闲超过此时间后将被终止 |
| unit | keepAliveTime的时间单位(如秒、毫秒等) |
| workQueue | 阻塞队列,用于存放待处理任务,当核心线程满负荷时新任务进入队列等待 |
| threadFactory | 线程工厂接口,可用于自定义线程名称、优先级、是否为守护线程等属性 |
| handler | 拒绝策略,当线程池饱和(最大线程数已达上限且队列已满)时如何处理新提交的任务 |
线程池的工作流程:
常见的拒绝策略:
AbortPolicy:默认策略,直接抛出 RejectedExecutionException 异常 RejectedExecutionException;CallerRunsPolicy:由调用者所在的线程(即提交任务的线程)直接执行该任务;DiscardPolicy:静默丢弃新任务,不抛异常;DiscardOldestPolicy:丢弃队列中最老的一个任务,然后尝试提交新任务。Executors工具类提供的常见线程池类型:
Executors.newFixedThreadPool(n):固定大小线程池,corePoolSize = maximumPoolSize = n,使用无界队列LinkedBlockingQueue;Executors.newCachedThreadPool():缓存线程池,corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,空闲线程存活60秒,使用SynchronousQueue;Executors.newSingleThreadExecutor():单线程池,保证任务按顺序执行,corePoolSize=1,maximumPoolSize=1,使用无界队列;Executors.newScheduledThreadPool(n):支持定时及周期性任务调度的线程池,底层使用DelayedWorkQueue。注意事项:
根据《阿里巴巴Java开发手册》建议,不应使用Executors创建线程池,原因在于:
newFixedThreadPool/newSingleThreadExecutor:采用无界队列,可能导致大量任务积压,引发OutOfMemoryError(OOM);newCachedThreadPool:最大线程数无限制,极端情况下可能创建过多线程,耗尽系统资源。因此推荐方式是:直接通过 ThreadPoolExecutor 构造函数手动配置参数,明确指定队列容量、线程边界和拒绝策略,提高系统的可控性和稳定性。
ThreadPoolExecutor构造方法应设置合理的参数,例如将核心线程数设定为CPU核心数±1,并优先采用有界队列作为任务队列。
ThreadLocal 是一种线程隔离的变量存储机制,它为每个线程提供独立的变量副本,从而避免多线程环境下共享变量带来的并发冲突。
每个 Thread 对象内部维护一个 ThreadLocalMap 实例(ThreadLocal 的静态内部类),该 Map 用于存储当前线程的本地变量。
ThreadLocalMap
set(T value):获取当前线程的 ThreadLocalMap,若不存在则创建,并将当前 ThreadLocal 实例作为 key,传入的 value 作为值存入。
set(T value)
get():获取当前线程的 ThreadLocalMap,根据当前 ThreadLocal 实例查找对应的值;若未找到,则调用 initialValue() 方法进行初始化并返回初始值。
get()
remove():从当前线程的 ThreadLocalMap 中删除与此 ThreadLocal 实例相关的键值对,释放资源。
initialValue()
remove()
由于 ThreadLocalMap 的 key 使用的是弱引用,当外部不再持有 ThreadLocal 实例的强引用时(如设为 null),key 会被垃圾回收,此时 entry 的 key 变为 null,但 value 仍被强引用指向。如果线程长期运行(如线程池中的核心线程),这些 value 将无法被 GC 回收,造成内存泄漏。
应对策略:
WeakReference<ThreadLocal<?>>
RequestContextHolder
JVM 在 JDK 8 版本下的运行时数据区域主要划分为五个部分:
程序计数器(Program Counter Register)
记录当前线程所执行的字节码指令地址(即行号),在线程切换时可恢复执行位置。
特点:线程私有,不会出现 OutOfMemoryError,是唯一不抛出 OOM 异常的区域。
虚拟机栈(VM Stack)
用于存放方法调用过程中的栈帧,每个栈帧包含局部变量表、操作数栈、方法出口等信息。
特点:线程私有,方法调用即入栈,方法返回即出栈,反映方法执行生命周期。
异常情况:
- 栈深度超过限制 → 抛出 StackOverflowError(常见于无限递归);
- 动态扩展时内存不足 → 抛出 OutOfMemoryError。
StackOverflowError
OutOfMemoryError
本地方法栈(Native Method Stack)
功能类似于虚拟机栈,专用于支持 Native 方法(如 Thread.start0())的执行。
特点:线程私有,也可能抛出 StackOverflowError 和 OutOfMemoryError。
堆(Heap)
用于存储对象实例和数组,是 JVM 中最大的内存区域,也是垃圾回收的主要场所。
特点:线程共享,可通过以下参数配置:
- -Xms:设置初始堆大小;
- -Xmx:设置最大堆大小。
-Xms
-Xmx
逻辑分区结构:
-XX:MetaspaceSize
-XX:MaxMetaspaceSize
异常情况:
- 堆内存不足 → 抛出 OutOfMemoryError;
- 元空间内存不足 → 抛出 Metaspace 相关的 OutOfMemoryError。
OutOfMemoryError: Java heap space
OutOfMemoryError: Metaspace
方法区(Method Area)
用于存储类的结构信息,包括类元数据、运行时常量池(String 常量池自 JDK 7 起移至堆中)、静态变量以及 JIT 编译后的代码。
特点:线程共享,在 JDK 8 之前称为“永久代”(PermGen),之后由“元空间”替代。
GC 是 JVM 自动管理堆内存的机制,旨在识别并回收不再使用的对象,释放内存空间,防止内存泄漏。
引用计数法
为每个对象维护一个引用计数器,每当有一个引用指向该对象时计数加一,引用失效则减一。当计数为零时视为可回收。
局限性:无法处理循环引用问题(如 A 引用 B,B 同时引用 A,导致计数均不为零而无法回收)。
可达性分析算法(JVM 实际采用)
以一组被称为“GC Roots”的对象为起点,向下搜索所有可达对象,未被访问到的对象被视为不可达,即为垃圾对象。
常见的 GC Roots 来源包括:
- 虚拟机栈中局部变量表引用的对象;
- 本地方法栈中 JNI 引用的对象;
- 方法区中静态成员引用的对象;
- 运行时常量池中的常量引用;
- 正在活跃的线程所持有的引用。
标记-清除算法(Mark-Sweep)
流程:先标记所有需要回收的对象,然后统一清除。
优点:实现简单,效率较高。
缺点:会产生大量内存碎片,可能导致后续大对象分配失败。
复制算法(Copying)
将可用内存按容量分为两块,每次只使用其中一块。当这一块用尽时,将存活对象复制到另一块上,再清空已使用过的区域。
优点:解决内存碎片问题,适合存活对象少的场景(如年轻代)。
缺点:内存利用率较低,需预留一半空间。
垃圾回收算法与常见收集器:
复制算法(Copying):
将内存划分为两个区域(例如 Eden 与 S0/S1),首先标记出存活的对象,随后将其复制到另一个空闲区域中,最后清空原区域。
优点:避免了内存碎片问题,内存分配效率高;
缺点:内存使用率仅为50%,存在空间浪费,适用于年轻代(该区域中大多数对象生命周期短、存活率低)。
<clinit>()
标记-整理算法(Mark-Compact):
首先标记所有存活对象,然后将这些对象向内存的一端进行移动,使得存活对象连续排列,最后清理边界以外的垃圾区域。
优点:无内存碎片,内存利用率高;
缺点:涉及对象移动操作,开销较大,适合用于老年代(此区域中多数对象长期存活)。
rt.jar
分代收集算法(Generational GC,JVM实际采用机制):
根据对象的生命周期将其划分到不同的代中,针对不同代的特点选用最合适的GC策略:
- 年轻代:对象死亡率高、存活少,采用复制算法;
- 老年代:对象存活多,通常采用标记-清除或标记-整理算法。
jre/lib/ext
常见的GC收集器:
ClassLoader
类加载过程概述:
类加载是指将 .class 字节码文件加载进JVM,并在内存中创建对应的 Class 对象的过程,整个流程分为五个阶段:
findClass()
类加载器类型:
java.lang.String
双亲委派模型(Parent Delegation Model):
核心机制:当一个类加载器收到类加载请求时,不会立即自行加载,而是先委托给其父类加载器去尝试加载,只有当父级无法完成加载时,才由自己处理。
典型路径:应用程序类加载器 → 扩展类加载器 → 启动类加载器。若顶层无法加载,则反向逐层尝试。
优势:
- 防止重复加载同一个类(例如 java.lang.String 只能由启动类加载器加载一次);
- 保障核心类库安全,防止被篡改或替换。
破坏双亲委派的典型场景:
Spring IoC 的原理与实现:
IoC(Inversion of Control,控制反转)是 Spring 框架的核心理念之一,即将对象的创建及依赖关系的维护从程序主动控制转交至Spring容器统一管理,从而降低组件间的耦合度。
关键概念:
ApplicationContext
BeanFactory
依赖注入的三种常用方式:
new构造方法注入:通过Bean的构造函数传入所依赖的对象。该方式适用于强制依赖项,能够有效防止空指针异常,是Spring官方推荐的方式之一。从Spring 4.3版本开始,若类中只有一个构造方法,@Autowired注解可省略。
@Service
public class UserService {
private final UserDao userDao;
// 构造方法注入(@Autowired可省略)
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
Setter方法注入:通过Setter方法设置依赖关系,适合用于可选的、非必需的依赖。需要配合@Autowired注解使用,Spring容器会自动调用对应的Setter方法完成注入。
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
字段注入:直接在成员变量上使用@Autowired注解进行注入,写法简洁,但不被推荐。因为这种方式绕过了构造器初始化,无法保证依赖的完整性,也不利于单元测试和依赖校验。
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
@Autowired
@Configuration
@Component
@Service
@Repository
@PostConstruct
afterPropertiesSet()
@PreDestroy
destroy()
AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架的重要特性之一,旨在将横切关注点(如日志、事务、安全控制等)与核心业务逻辑分离,提升代码的模块化程度和复用性。
@Before:前置通知,在目标方法执行前运行;@After:最终通知,无论方法是否抛出异常都会执行;@AfterReturning:返回通知,仅在方法成功返回后触发;@AfterThrowing:异常通知,当目标方法抛出异常时执行;@Around:环绕通知,包裹整个方法调用过程,可控制是否继续执行目标方法,常用于事务控制;Spring AOP 主要依赖两种动态代理技术来实现织入:
JDK 动态代理:
java.lang.reflect.Proxy
CGLIB 动态代理:
以下是一个基于@AspectJ风格的日志切面示例:
// 切面类
@Aspect
@Component
public class LogAspect {
// 定义切入点:拦截com.example.service包下所有public方法
@Pointcut("execution(public * com.example.service..*(..))")
public void servicePointcut() {}
// 环绕通知:记录方法执行时间
@Around("servicePointcut()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行目标方法
long duration = System.currentTimeMillis() - startTime;
System.out.println("Method " + joinPoint.getSignature() + " executed in " + duration + "ms");
return result;
}
}
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法名和参数
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("方法" + methodName + "调用,参数:" + Arrays.toString(args));
// 记录开始时间,执行目标方法
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
// 方法执行完成后,计算耗时并输出结果
long cost = System.currentTimeMillis() - start;
System.out.println("方法" + methodName + "返回值:" + result + ",执行时间:" + cost + "ms");
return result;
}
Spring事务管理主要分为两种方式:声明式事务(基于AOP实现)和编程式事务(通过编码手动控制)。其中,声明式事务因其简洁性和非侵入性,成为开发中的主流选择。
Spring允许配置不同的事务隔离级别,以平衡并发性能与数据一致性:
DEFAULT:默认级别,具体由底层数据库决定(如MySQL默认为REPEATABLE READ);READ_UNCOMMITTED:读未提交,最低级别,可能导致脏读、不可重复读和幻读;READ_COMMITTED:读已提交,可防止脏读,但不可重复读和幻读仍可能发生(Oracle默认);REPEATABLE_READ:可重复读,避免脏读和不可重复读,但在某些情况下可能出现幻读(MySQL默认);SERIALIZABLE:串行化,最高级别,彻底解决并发问题,但会显著降低系统性能。
Spring定义了7种事务传播机制,常见如下:
REQUIRED:若当前存在事务,则加入该事务;否则创建新事务(默认行为);REQUIRES_NEW:无论当前是否有事务,均开启一个新的独立事务,原事务被挂起;SUPPORTS:若当前有事务则加入,没有则以非事务方式运行;NOT_SUPPORTED:始终以非事务方式执行,若当前存在事务则将其暂停;NEVER:非事务方式执行,若当前存在事务则抛出异常。
声明式事务通过注解驱动,核心流程如下:
@EnableTransactionManagement 注解开启事务支持(Spring Boot项目通常自动启用);@Transactional 可标注在类或方法上,类级别注解作用于所有公共方法;@Transactional 被解析为切面,切入点为目标方法;PlatformTransactionManager 接口统一管理不同数据访问技术;@EnableTransactionManagement:启用事务管理;@Transactional:标注事务方法;@Transactional:AOP切面处理流程;PlatformTransactionManager:事务管理器接口;DataSourceTransactionManager:适用于JDBC或MyBatis的数据源事务管理;HibernateTransactionManager:适配Hibernate的事务实现。
public —— @Transactional 仅对 public 方法有效;RuntimeException 和 Error 回滚,检查型异常需显式声明;try-catch 捕获异常但未重新抛出 —— 事务切面无法感知异常,导致无法触发回滚;NOT_SUPPORTED 或 NEVER 会导致事务被挂起或拒绝;DataSourceTransactionManager 未被Spring容器管理,导致事务配置无效。RuntimeException:运行时异常;Error:错误类型;rollbackFor:用于指定回滚的异常类型;try-catch:捕获异常代码块;throw:未抛出异常语句;PlatformTransactionManager:事务管理器未注册到Spring上下文中。
JDBC(Java Database Connectivity)是Java程序连接和操作数据库的标准API,基本流程包括:
DriverManager.getConnection() 建立与数据库的连接;Statement 或 PreparedStatement 实例;executeQuery() 方法;executeUpdate() 方法;DriverManager.getConnection(url, username, password):建立数据库连接;executeQuery():执行查询语句;executeUpdate():执行增删改操作。数据库操作流程主要包括以下几个步骤:
示例代码如下:
public void queryUser() {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 加载 MySQL 驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立数据库连接
String url = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
conn = DriverManager.getConnection(url, "root", "123456");
// 定义带参数的 SQL 查询语句
String sql = "SELECT id, name FROM user WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1); // 绑定参数
// 执行查询并获取结果集
rs = pstmt.executeQuery();
// 遍历结果集并输出信息
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println("id: " + id + ", name: " + name);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 按顺序关闭资源,避免内存泄漏
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
SELECT * FROM user WHERE name = '" + name + "'"
关于 SQL 注入问题,若使用 Statement 拼接字符串构造 SQL,当传入的参数如 name 包含恶意内容
' OR '1'='1 时,最终生成的 SQL 将变为 SELECT * FROM user WHERE name = '' OR '1'='1',可能导致所有用户数据被泄露。而 PreparedStatement 使用参数绑定机制,在SQL语法解析前完成参数设置,从根本上杜绝了此类风险。
与 Statement 相比,PreparedStatement 的优势包括:
MyBatis 是一个优秀的持久层框架,它简化了传统 JDBC 编码过程,允许开发者通过 XML 文件或注解方式配置 SQL 语句,无需手动管理连接、结果集等资源。
SqlSessionFactory:SqlSessionFactory 是 MyBatis 的核心工厂类,由 SqlSessionFactoryBuilder 解析全局配置文件 mybatis-config.xml 后构建而成,通常以单例形式存在,线程安全。
SqlSession:SqlSession 表示一次数据库会话,封装了底层 Connection 和事务控制逻辑。由于其内部状态不具线程安全性,建议每个请求独立创建实例。
Mapper接口:Mapper 接口 是用户自定义的数据访问接口,MyBatis 利用动态代理技术自动生成其实现类,将接口方法调用映射到对应的 SQL 执行。
Mapper.xml:Mapper 映射文件 或注解中定义了具体的 SQL 语句、输入参数类型及结果集映射规则,实现 Java 对象与数据库记录之间的转换。
Configuration:Configuration 对象保存 MyBatis 的全部配置信息,包括数据源、事务管理器、缓存设置以及注册的所有 Mapper。
SqlSessionFactoryBuilder 负责读取 mybatis-config.xml 和各个 Mapper.xml 文件,解析出数据库连接信息、SQL 定义、映射关系等元数据。openSession() 方法创建 SqlSession,默认情况下事务不会自动提交。getMapper(Mapper接口.class) 方法,获得指定 Mapper 接口的代理实现。commit() 提交事务,或调用 rollback() 回滚事务。MyBatis的一级缓存与二级缓存机制解析:
为了提升数据库操作性能,MyBatis内置了缓存功能,能够有效减少对数据库的重复查询。该缓存体系主要分为两个层级:一级缓存和二级缓存。
SqlSession.clearCache()
<cache/>标签,以激活该Mapper的二级缓存功能。<setting name="cacheEnabled" value="true"/>
<cache/>
Serializable
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
</mappers>
</configuration>
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="UserResultMap" type="com.example.entity.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
</resultMap>
<select id="selectUserById" parameterType="int" resultMap="UserResultMap">
SELECT id, name, age FROM user WHERE id = #{id}
</select>
<insert id="insertUser" parameterType="com.example.entity.User">
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
</mapper>
执行插入(insert)、更新(update)或删除(delete)操作时,会清空当前Mapper的二级缓存。
可通过以下方式控制缓存行为:
useCache="false" —— 用于查询语句
flushCache="true" —— 用于增删改语句
缓存的查询顺序如下:首先尝试从二级缓存获取数据,若未命中则查找一级缓存,最后再访问数据库。
单例模式的核心目标是确保一个类在整个应用中仅存在一个实例,并提供全局访问点。常见的实现方式包括以下几种:
(1)饿汉式(线程安全,非懒加载)
public class Singleton {
// 类加载阶段即创建实例
private static final Singleton instance = new Singleton();
// 私有构造函数,防止外部实例化
private Singleton() {}
// 全局访问方法
public static Singleton getInstance() {
return instance;
}
}
(2)懒汉式(从非线程安全到线程安全的演进)
基础版本(线程不安全)
public class Singleton {
private static Singleton instance;
private Singleton() {}
// 多线程环境下可能产生多个实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
该版本在高并发场景下可能导致多个线程同时进入if判断,从而创建多个实例。
优化版本——双重检查锁定(DCL),支持懒加载且线程安全
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;
}
}
volatile关键字,防止instance = new Singleton()所示的指令重排序问题(如内存分配、初始化和引用赋值顺序被打乱),否则其他线程可能获取到尚未完全初始化的对象。(3)静态内部类方式(推荐方案,线程安全且支持懒加载)
public class Singleton {
private Singleton() {}
// 静态内部类,只有在调用getInstance时才会加载
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
(4)枚举方式(最佳实践,防反射与序列化攻击)
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Singleton enum");
}
}
Java系统的性能优化需要从多个层面协同推进,主要包括代码层、JVM层、数据库层以及系统架构层,目标在于提升响应效率并降低资源消耗。
(1)代码层面优化
集合类使用建议
ArrayList.add(index, element)这类时间复杂度为O(n)的操作,尤其是当集合规模较大时。字符串处理优化
在Java开发中,字符串拼接操作应优先使用StringBuilder(适用于单线程场景)或StringBuffer(适用于多线程环境),以避免频繁使用String进行拼接所带来的性能问题。由于String是不可变对象,每次拼接都会创建新的临时对象,增加GC负担。
对于常量字符串的处理,应当充分利用JVM的字符串常量池机制,通过直接赋值或调用intern()方法复用已有对象,减少重复对象的生成。
String.intern()
在循环结构中,应注意以下优化点:
for (int i = 0; i < list.size(); i++)
int size = list.size(); for (int i = 0; i < size; i++)
Objects.requireNonNull()
null
资源管理方面,务必采用try-with-resources语法结构,确保输入输出流、数据库连接等资源在使用完毕后能自动关闭,有效防止资源泄漏问题。
堆内存配置需合理设定-Xms(初始堆大小)和-Xmx(最大堆大小)。建议将两者设置为相同值,以避免运行时频繁扩容带来的性能波动。通常情况下,堆内存可设为物理内存的1/2至1/3之间。
-Xms
-Xmx
年轻代空间的划分也影响GC效率。可通过调整-XX:NewRatio参数控制年轻代与老年代的比例(默认为2:1),并结合应用中对象的实际存活时间进行优化。同时,Eden区与Survivor区的比例可通过-XX:SurvivorRatio参数调节(默认8:1)。
-XX:NewRatio
-XX:SurvivorRatio
垃圾收集器的选择应根据业务需求而定:
此外,JVM默认开启逃逸分析功能(-XX:+DoEscapeAnalysis),该机制可识别未发生逃逸的对象,并将其分配在线程栈上而非堆中,从而减轻GC压力,提升执行效率。
-XX:+DoEscapeAnalysis
合理建立索引是提升查询速度的核心手段。应对WHERE条件、JOIN关联字段以及ORDER BY排序字段添加索引,但需注意避免过度建索引,以免影响INSERT、UPDATE和DELETE操作的性能。
SQL语句编写时应遵循以下原则:
WHERE DATE(create_time) = '2025-01-01'
batchInsert
数据库连接池的配置同样重要。以HikariCP为例,其maximumPoolSize建议设置为CPU核心数的2倍加1,既能充分利用系统资源,又可防止连接过多导致上下文切换开销。同时要确保连接正确释放,杜绝连接泄漏。
maximum-pool-size
引入缓存机制可显著降低数据库负载。可采用Redis、Ehcache等缓存中间件存储热点数据,如用户会话信息、系统配置项等,提升访问速度。
将非关键路径的耗时任务异步化处理,例如日志记录、邮件发送等,可通过线程池或消息队列实现解耦,提高主流程响应性能。
通过Nginx、LVS等负载均衡技术,将请求均匀分发至多个服务节点,有效分散单机压力,增强系统的可用性与伸缩能力。
实施分布式部署策略,将单体应用拆分为多个微服务模块,按业务边界独立部署与扩展,有助于提升整体并发处理能力和系统稳定性。
本文系统梳理了Java面试中的核心知识点体系,涵盖基础语法、集合框架、多线程编程、JVM原理、Spring生态、数据库操作、设计模式及性能调优等多个维度。每个模块均整合了高频考题、详尽解答与深层原理剖析,兼顾理论理解与实际应用。
在准备面试时,不应仅停留在记忆答案层面,更需深入理解底层机制,例如HashMap如何解决哈希冲突、Spring AOP基于动态代理的实现方式、JVM中各类GC算法的工作原理等。同时,结合个人项目经验阐述技术的实际落地场景,如线程池的具体参数配置过程、事务失效问题的排查思路等,能够显著增强回答的专业性和说服力。
建议重点掌握多线程并发控制、JVM调优策略以及Spring核心原理等进阶内容,这些往往是区分初级开发者与中高级工程师的关键能力指标。
扫码加好友,拉您进群



收藏
