常见面试题
以下是一些常见的Java面试题及解答思路,涵盖基础知识、高级特性、框架和项目经验等方面:
一、基础知识
1. 面向对象编程(OOP)
问题:什么是面向对象编程?Java有哪些特性体现了OOP?
解答思路:
- 核心概念:封装、继承、多态、抽象。
- 结合Java代码举例:
- 封装:通过
private修饰符隐藏类的内部状态,提供public方法访问。 - 继承:使用
extends关键字实现类的复用。 - 多态:通过接口或抽象类实现方法的动态绑定(如
List list = new ArrayList())。 - 抽象:使用
abstract关键字定义抽象类或方法。
- 封装:通过
2. String、StringBuilder、StringBuffer的区别
解答思路:
- String:不可变对象,每次操作都会生成新对象,适合少量字符串操作。
- StringBuilder:可变对象,非线程安全,性能高(推荐在单线程环境使用)。
- StringBuffer:可变对象,线程安全(内部方法用
synchronized修饰),性能低。 - 场景选择:循环中拼接字符串用
StringBuilder,多线程环境用StringBuffer。
3. 自动装箱与拆箱
问题:解释Integer i = 10和int j = i的过程。
解答思路:
- 装箱:
Integer i = 10等价于Integer i = Integer.valueOf(10)。 - 拆箱:
int j = i等价于int j = i.intValue()。 - 注意点:
Integer缓存池(默认范围-128~127),如Integer a = 100; Integer b = 100; a==b返回true。
二、集合框架
1. HashMap的工作原理(Java 8+)
解答思路:
- 数据结构:数组+链表+红黑树(当链表长度≥8且数组长度≥64时,链表转为红黑树)。
- put过程:
- 计算key的hash值,定位数组索引。
- 若索引位置为空,直接插入;否则遍历链表/红黑树,存在相同key则覆盖,否则插入。
- 扩容机制:当元素数量超过阈值(容量×负载因子,默认0.75)时,数组扩容为原来的2倍。
- 线程安全:非线程安全,多线程环境用
ConcurrentHashMap。
2. ArrayList vs LinkedList
解答思路:
- 底层结构:
ArrayList:动态数组,支持随机访问(O(1)),插入/删除效率低(O(n))。LinkedList:双向链表,插入/删除效率高(O(1)),随机访问效率低(O(n))。
- 适用场景:频繁随机访问用
ArrayList,频繁插入删除用LinkedList。
三、多线程与并发
1. synchronized与ReentrantLock的区别
解答思路:
- 语法层面:
synchronized:Java内置关键字,自动释放锁(代码块/方法结束时)。ReentrantLock:JDK类,需手动调用lock()和unlock()。
- 功能特性:
ReentrantLock:可中断锁、公平锁、尝试锁(tryLock())。
- 性能:
- JDK 6+对
synchronized优化(偏向锁、轻量级锁),性能接近ReentrantLock。
- JDK 6+对
2. 线程池的核心参数及工作流程
解答思路:
- 核心参数(
ThreadPoolExecutor):corePoolSize:核心线程数。maximumPoolSize:最大线程数。workQueue:任务队列(如LinkedBlockingQueue)。keepAliveTime:空闲线程存活时间。RejectedExecutionHandler:拒绝策略(如AbortPolicy)。
- 工作流程:
- 提交任务,若核心线程未满,创建新线程执行。
- 若核心线程已满,任务入队。
- 若队列已满且线程数未达最大,创建新线程执行。
- 若线程数已达最大,触发拒绝策略。
四、JVM底层原理
1. JVM内存区域划分
解答思路:
- 堆(Heap):存储对象实例,线程共享,可分为新生代(Eden、Survivor)和老年代。
- 栈(Stack):存储局部变量和方法调用帧,线程私有。
- 方法区(Method Area):存储类信息、常量池等,线程共享(Java 8后改为元空间MetaSpace)。
- 程序计数器(PC Register):记录当前线程执行的字节码行号。
- 本地方法栈(Native Method Stack):为Native方法服务。
2. 垃圾回收(GC)机制
解答思路:
- 可达性分析:从GC Roots(如栈变量、静态变量)出发,标记不可达对象。
- 垃圾收集算法:
- 标记-清除(Mark-Sweep):产生内存碎片。
- 标记-整理(Mark-Compact):避免碎片。
- 复制(Copying):新生代常用(Eden:Survivor=8:1:1)。
- 垃圾收集器:
- 新生代:Serial、ParNew、Parallel Scavenge。
- 老年代:CMS、Serial Old、Parallel Old。
- 全堆:G1、ZGC。
五、框架与工具
1. Spring框架的核心特性
解答思路:
- IOC(控制反转):通过XML或注解配置,由容器管理对象的创建和依赖注入。
- AOP(面向切面编程):通过动态代理(JDK或CGLIB)实现横切关注点(如日志、事务)。
- 事务管理:声明式事务(基于AOP)和编程式事务。
- MVC:
DispatcherServlet处理请求,@Controller和@RequestMapping注解。
2. MyBatis与Hibernate的区别
解答思路:
- ORM程度:
- MyBatis:半ORM,需手动编写SQL,灵活控制查询。
- Hibernate:全ORM,通过对象映射自动生成SQL,适合快速开发。
- 适用场景:
- MyBatis:复杂SQL场景(如报表)。
- Hibernate:业务逻辑简单、对象关系复杂的场景。
六、项目经验与场景题
1. 如何优化高并发系统?
解答思路:
- 前端:静态资源CDN、页面缓存、懒加载。
- 服务端:
- 负载均衡(Nginx、LVS)。
- 异步处理(消息队列RabbitMQ/Kafka)。
- 限流熔断(Sentinel、Hystrix)。
- 存储层:
- 缓存(Redis)。
- 分库分表(ShardingSphere)。
- 读写分离。
2. 如何解决分布式事务问题?
解答思路:
- 刚性事务:2PC(两阶段提交)、3PC(三阶段提交)。
- 柔性事务:
- TCC(Try-Confirm-Cancel)。
- 补偿事务(Saga模式)。
- 最终一致性(基于消息队列)。
七、算法与数据结构
1. 手写快速排序
解答思路:
public void quickSort(int[] arr, int left, int right) {
if (left < right) {
int pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}
}
private int partition(int[] arr, int left, int right) {
int pivot = arr[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, right);
return i + 1;
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
2. 设计LRU缓存
解答思路:
- 使用
LinkedHashMap实现,重写removeEldestEntry()方法:
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
};
}
}
八、总结
面试时需注意:
- 清晰表达思路:先解释概念,再结合代码或场景举例。
- 深入源码或原理:如HashMap的红黑树转换条件、synchronized的锁升级过程。
- 关联实际项目:用项目中的案例说明技术选择或问题解决过程。
- 对答如流的“高频题”:HashMap、多线程、JVM GC、Spring原理。