Java后端开发实习生面试题及回答详解

Java后端开发实习生面试题及回答详解

目录

  1. Java基础
  2. 面向对象编程
  3. 集合框架
  4. 多线程与并发
  5. JVM内存管理
  6. 数据库相关
  7. Spring框架
  8. 项目经验与场景题
  9. 算法与数据结构
  10. 其他技术栈

Java基础

Q1: Java的特点是什么?

回答要点:

  • 面向对象:封装、继承、多态
  • 平台无关性:一次编写,到处运行(JVM)
  • 自动内存管理:垃圾回收机制
  • 多线程支持:内置多线程机制
  • 安全性:字节码验证、安全管理器
  • 健壮性:异常处理机制

示例回答:

Java是一门面向对象的编程语言,具有平台无关性,通过JVM实现"一次编写,到处运行"。
Java提供了自动内存管理机制(GC),减少了内存泄漏的风险。同时Java内置了多线程支持,
异常处理机制使得程序更加健壮。Java还具有良好的安全性,通过字节码验证和安全管理器
来保证程序安全运行。

Q2: Java的基本数据类型有哪些?它们占用的字节数是多少?

回答要点:

数据类型 字节数 取值范围 默认值
byte 1 -128 ~ 127 0
short 2 -2^15 ~ 2^15-1 0
int 4 -2^31 ~ 2^31-1 0
long 8 -2^63 ~ 2^63-1 0L
float 4 IEEE 754 0.0f
double 8 IEEE 754 0.0d
char 2 0 ~ 65535 ‘\u0000’
boolean 1位 true/false false

记忆技巧:

  • 整数类型:byte(1) < short(2) < int(4) < long(8)
  • 浮点类型:float(4) < double(8)
  • char是2字节(Unicode字符)

Q3: == 和 equals() 的区别?

回答要点:

// == 比较的是引用地址
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); // false,不同对象

// equals() 比较的是内容(String类重写了equals方法)
System.out.println(str1.equals(str2)); // true

// 特殊情况:字符串常量池
String str3 = "hello";
String str4 = "hello";
System.out.println(str3 == str4); // true,指向常量池同一对象

核心区别:

  • ==:比较基本数据类型时比较值,比较引用类型时比较内存地址
  • equals():Object类中默认比较地址,但很多类(如String、Integer)重写了equals方法,比较内容

面试加分点:

// 重写equals()的规范
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 同一对象
if (obj == null || getClass() != obj.getClass()) return false; // 类型检查
Person person = (Person) obj;
return Objects.equals(name, person.name) &&
Objects.equals(age, person.age);
}

// 重写equals()必须重写hashCode()
@Override
public int hashCode() {
return Objects.hash(name, age);
}

Q4: String、StringBuilder、StringBuffer的区别?

回答要点:

可变性 线程安全 性能 使用场景
String 不可变 安全 慢(频繁拼接) 字符串常量
StringBuilder 可变 不安全 单线程字符串拼接
StringBuffer 可变 安全(synchronized) 中等 多线程字符串拼接

代码示例:

// String - 不可变,每次拼接都创建新对象
String str = "Hello";
str += " World"; // 创建了新对象,原对象被GC

// StringBuilder - 单线程推荐
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" World"); // 在原对象上修改

// StringBuffer - 多线程安全
StringBuffer sbf = new StringBuffer();
sbf.append("Hello").append(" World"); // 线程安全,但性能较低

Q5: final关键字的作用?

回答要点:

  1. 修饰变量:变量只能赋值一次,成为常量

    final int x = 10;
    // x = 20; // 编译错误
  2. 修饰方法:方法不能被重写

    class Parent {
    final void method() { } // 子类不能重写
    }
  3. 修饰类:类不能被继承

    final class MyClass { }  // 不能被继承
    // class Child extends MyClass { } // 编译错误

面试加分点:

  • final修饰引用类型时,引用不能改变,但对象内容可以改变

    final List<String> list = new ArrayList<>();
    list.add("hello"); // 可以
    // list = new ArrayList<>(); // 编译错误

面向对象编程

Q6: 面向对象的三大特性是什么?请详细说明。

回答要点:

1. 封装(Encapsulation)

  • 定义:隐藏对象的内部实现细节,只暴露必要的接口
  • 好处:提高代码安全性、可维护性
  • 实现:使用private修饰属性,提供getter/setter方法
public class Student {
private String name; // 私有属性
private int age;

// 提供公共方法访问
public String getName() {
return name;
}

public void setName(String name) {
if (name != null && !name.isEmpty()) {
this.name = name;
}
}
}

2. 继承(Inheritance)

  • 定义:子类继承父类的属性和方法
  • 好处:代码复用,提高可维护性
  • 特点:Java单继承,但可以实现多个接口
class Animal {
protected String name;
public void eat() {
System.out.println("eating...");
}
}

class Dog extends Animal { // 继承
public void bark() {
System.out.println("barking...");
}
}

3. 多态(Polymorphism)

  • 定义:同一接口的不同实现
  • 实现方式:方法重写、方法重载、接口实现
  • 运行时多态:通过方法重写实现
class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
}

class Dog extends Animal {
@Override
public void makeSound() { // 重写
System.out.println("Woof!");
}
}

// 多态体现
Animal animal = new Dog();
animal.makeSound(); // 输出 "Woof!",运行时确定

Q7: 抽象类和接口的区别?

回答要点:

特性 抽象类 接口
关键字 abstract class interface
继承/实现 extends implements
方法 可以有抽象方法和具体方法 Java 8前只能有抽象方法
变量 可以有普通变量 只能是public static final
构造方法 可以有 不能有
继承 单继承 可以实现多个接口
访问修饰符 任意 public(默认)

代码示例:

// 抽象类
abstract class Animal {
protected String name; // 可以有成员变量

public Animal(String name) { // 可以有构造方法
this.name = name;
}

abstract void makeSound(); // 抽象方法

public void sleep() { // 具体方法
System.out.println("sleeping...");
}
}

// 接口(Java 8+)
interface Flyable {
int MAX_SPEED = 100; // public static final

void fly(); // 抽象方法

// Java 8:默认方法
default void land() {
System.out.println("landing...");
}

// Java 8:静态方法
static void info() {
System.out.println("This is a flyable interface");
}
}

使用场景:

  • 抽象类:有共同属性和行为的类,需要构造方法
  • 接口:定义规范,实现多继承效果

Q8: 重写(Override)和重载(Overload)的区别?

回答要点:

特性 重写(Override) 重载(Overload)
定义 子类重写父类方法 同一类中方法名相同,参数不同
方法名 必须相同 必须相同
参数列表 必须相同 必须不同
返回类型 相同或子类 可以不同
访问修饰符 不能更严格 可以不同
异常 不能抛出更广泛的异常 可以不同
发生位置 父子类之间 同一类中

代码示例:

class Parent {
public void method(int x) {
System.out.println("Parent: " + x);
}
}

class Child extends Parent {
// 重写
@Override
public void method(int x) {
System.out.println("Child: " + x);
}

// 重载(同一类中)
public void method(String s) {
System.out.println("Overload: " + s);
}

public void method(int x, int y) {
System.out.println("Overload: " + x + ", " + y);
}
}

集合框架

Q9: 请介绍Java集合框架的体系结构?

回答要点:

Collection(接口)
├── List(有序,可重复)
│ ├── ArrayList(数组实现,随机访问快)
│ ├── LinkedList(链表实现,插入删除快)
│ └── Vector(线程安全,已过时)

├── Set(无序,不可重复)
│ ├── HashSet(哈希表实现)
│ ├── LinkedHashSet(保持插入顺序)
│ └── TreeSet(红黑树,有序)

└── Queue(队列)
├── PriorityQueue(优先级队列)
└── Deque(双端队列)

Map(接口,独立于Collection)
├── HashMap(哈希表,无序)
├── LinkedHashMap(保持插入顺序)
├── TreeMap(红黑树,有序)
└── Hashtable(线程安全,已过时)

Q10: ArrayList和LinkedList的区别?

回答要点:

特性 ArrayList LinkedList
底层结构 动态数组 双向链表
随机访问 O(1) O(n)
插入/删除 O(n) O(1)(已知位置)
内存占用 连续内存 分散内存(指针开销)
适用场景 频繁查询 频繁插入删除

代码示例:

// ArrayList - 适合查询
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.get(0); // O(1) 快速访问
arrayList.add(0, "B"); // O(n) 需要移动元素

// LinkedList - 适合插入删除
List<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.get(0); // O(n) 需要遍历
linkedList.addFirst("B"); // O(1) 快速插入

Q11: HashMap的底层原理?

回答要点:

JDK 1.8之前

  • 结构:数组 + 链表
  • 哈希冲突:使用链表解决
  • 问题:链表过长时查询效率低

JDK 1.8之后

  • 结构:数组 + 链表 + 红黑树
  • 优化:链表长度 > 8 时转换为红黑树
  • 扩容:负载因子0.75,容量2倍扩容

核心机制:

// 1. 计算hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

// 2. 确定数组索引
index = (n - 1) & hash // n是数组长度

// 3. 插入逻辑
// - 如果位置为空,直接插入
// - 如果位置有值,判断key是否相等
// - 相等:覆盖value
// - 不等:插入链表/红黑树

面试加分点:

// 为什么容量是2的幂次?
// 因为 (n-1) & hash 等价于 hash % n,但位运算更快
// 且能保证分布均匀

// 为什么负载因子是0.75?
// 平衡时间和空间:太小浪费空间,太大增加冲突

// 为什么链表转红黑树的阈值是8?
// 红黑树平均查找长度log(n),链表是n/2
// 当n=8时,log(8)=3 < 8/2=4,红黑树更优

Q12: HashMap和Hashtable的区别?

回答要点:

特性 HashMap Hashtable
线程安全 不安全 安全(synchronized)
null值 允许一个null key,多个null value 不允许null
效率 低(同步开销)
继承 AbstractMap Dictionary
迭代器 Fail-Fast Fail-Safe
版本 JDK 1.2+ JDK 1.0(已过时)

推荐使用:

  • 单线程:HashMap
  • 多线程:ConcurrentHashMap(推荐)或 Hashtable

Q13: HashSet的底层实现原理?

回答要点:

// HashSet底层使用HashMap实现
public class HashSet<E> {
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();

public boolean add(E e) {
return map.put(e, PRESENT) == null; // value固定为PRESENT
}
}

特点:

  • 使用HashMap的key存储元素
  • value统一为PRESENT常量
  • 因此HashSet的特性与HashMap的key特性一致

多线程与并发

Q14: 创建线程的方式有哪些?

回答要点:

方式1:继承Thread类

class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}

MyThread thread = new MyThread();
thread.start();

方式2:实现Runnable接口(推荐)

class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}

Thread thread = new Thread(new MyRunnable());
thread.start();

方式3:实现Callable接口(有返回值)

class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}

FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get(); // 获取返回值

方式4:线程池(实际开发推荐)

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {
System.out.println("Thread pool running");
});

推荐使用Runnable的原因:

  • Java单继承,继承Thread后无法继承其他类
  • Runnable更灵活,可以传入线程池
  • 符合面向接口编程原则

Q15: 线程的生命周期(状态)?

回答要点:

NEW(新建)
↓ start()
RUNNABLE(可运行)
↓ 获取CPU时间片
RUNNING(运行中)
↓ wait() / sleep() / join() / 等待IO
BLOCKED(阻塞)
↓ notify() / notifyAll() / 时间到 / IO完成
RUNNABLE
↓ run()结束 / 异常
TERMINATED(终止)

Java中的6种状态(Thread.State):

public enum State {
NEW, // 新建,未调用start()
RUNNABLE, // 可运行(包括运行中)
BLOCKED, // 阻塞(等待锁)
WAITING, // 等待(wait/join)
TIMED_WAITING, // 超时等待(sleep/wait(timeout))
TERMINATED // 终止
}

Q16: sleep()和wait()的区别?

回答要点:

特性 sleep() wait()
所属类 Thread Object
释放锁 不释放 释放
使用位置 任何地方 同步代码块/方法中
唤醒方式 时间到自动唤醒 notify()/notifyAll()
异常 InterruptedException InterruptedException

代码示例:

// sleep() - 不释放锁
synchronized (obj) {
Thread.sleep(1000); // 持有锁,其他线程无法进入
}

// wait() - 释放锁
synchronized (obj) {
obj.wait(); // 释放锁,其他线程可以获取锁
// 被notify()唤醒后重新竞争锁
}

Q17: synchronized关键字的作用?

回答要点:

1. 修饰实例方法

public synchronized void method() {
// 锁的是当前对象实例
}

2. 修饰静态方法

public static synchronized void method() {
// 锁的是当前类的Class对象
}

3. 修饰代码块

synchronized (obj) {
// 锁的是指定对象
}

原理:

  • JVM层面:通过monitorenter和monitorexit指令实现
  • 对象头:每个对象都有monitor(监视器锁)
  • 可重入:同一线程可以多次获取同一把锁

面试加分点:

// synchronized的优化(JDK 1.6+)
// 1. 偏向锁:第一次获取锁时记录线程ID
// 2. 轻量级锁:CAS自旋,适合锁竞争不激烈
// 3. 重量级锁:真正的互斥,适合锁竞争激烈

Q18: volatile关键字的作用?

回答要点:

1. 保证可见性

// 不使用volatile
boolean flag = false; // 线程A修改后,线程B可能看不到

// 使用volatile
volatile boolean flag = false; // 修改后立即刷新到主内存

2. 禁止指令重排序

// 双重检查锁定单例模式
class Singleton {
private static volatile Singleton instance;

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // volatile防止重排序
}
}
}
return instance;
}
}

注意:volatile不保证原子性

volatile int count = 0;
count++; // 不是原子操作,需要synchronized或AtomicInteger

Q19: 线程池的核心参数有哪些?

回答要点:

ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)

执行流程:

任务提交

核心线程是否已满?
├─ 否 → 创建核心线程执行
└─ 是 → 队列是否已满?
├─ 否 → 入队等待
└─ 是 → 线程数是否达到最大?
├─ 否 → 创建非核心线程执行
└─ 是 → 执行拒绝策略

拒绝策略:

  • AbortPolicy:抛出异常(默认)
  • CallerRunsPolicy:调用者线程执行
  • DiscardPolicy:直接丢弃
  • DiscardOldestPolicy:丢弃最旧的任务

JVM内存管理

Q20: JVM内存区域划分?

回答要点:

JVM内存区域
├── 线程私有
│ ├── 程序计数器(PC Register)
│ ├── 虚拟机栈(VM Stack)
│ └── 本地方法栈(Native Method Stack)

└── 线程共享
├── 堆(Heap)
│ ├── 新生代(Young Generation)
│ │ ├── Eden区
│ │ └── Survivor区(S0, S1)
│ └── 老年代(Old Generation)
└── 方法区(Method Area / Metaspace)

各区域说明:

  • 程序计数器:记录当前执行的字节码指令地址
  • 虚拟机栈:存储局部变量、方法参数、返回地址
  • 本地方法栈:为Native方法服务
  • :存放对象实例,GC主要区域
  • 方法区:存储类信息、常量、静态变量(JDK 8后改为Metaspace)

Q21: 垃圾回收算法有哪些?

回答要点:

1. 标记-清除(Mark-Sweep)

  • 过程:标记需要回收的对象,然后清除
  • 缺点:产生内存碎片

2. 复制算法(Copying)

  • 过程:将存活对象复制到另一块内存
  • 适用:新生代(Eden和Survivor)
  • 缺点:内存利用率低

3. 标记-整理(Mark-Compact)

  • 过程:标记后,将存活对象向一端移动
  • 适用:老年代
  • 优点:无碎片

4. 分代收集(Generational)

  • 新生代:复制算法(Minor GC)
  • 老年代:标记-清除/标记-整理(Major GC / Full GC)

Q22: 常见的垃圾收集器有哪些?

回答要点:

收集器 区域 算法 特点
Serial 新生代 复制 单线程,STW
ParNew 新生代 复制 Serial的多线程版
Parallel Scavenge 新生代 复制 吞吐量优先
Serial Old 老年代 标记-整理 Serial的老年代版
Parallel Old 老年代 标记-整理 Parallel Scavenge的老年代版
CMS 老年代 标记-清除 并发收集,低延迟
G1 全堆 标记-整理+复制 可预测停顿时间
ZGC 全堆 标记-整理 超低延迟(JDK 11+)

常用组合:

  • ParNew + CMS:低延迟
  • Parallel Scavenge + Parallel Old:高吞吐量
  • G1:平衡(JDK 9默认)

数据库相关

Q23: SQL的几种连接方式?

回答要点:

-- 内连接(INNER JOIN):只返回两表都有的记录
SELECT * FROM A INNER JOIN B ON A.id = B.id;

-- 左连接(LEFT JOIN):返回左表所有记录,右表没有的为NULL
SELECT * FROM A LEFT JOIN B ON A.id = B.id;

-- 右连接(RIGHT JOIN):返回右表所有记录,左表没有的为NULL
SELECT * FROM A RIGHT JOIN B ON A.id = B.id;

-- 全连接(FULL JOIN):返回两表所有记录
SELECT * FROM A FULL JOIN B ON A.id = B.id;

Q24: 数据库事务的ACID特性?

回答要点:

  • Atomicity(原子性):事务要么全部成功,要么全部失败
  • Consistency(一致性):事务前后数据保持一致
  • Isolation(隔离性):并发事务之间相互隔离
  • Durability(持久性):事务提交后数据永久保存

隔离级别:

  • READ UNCOMMITTED:读未提交(最低)
  • READ COMMITTED:读已提交(Oracle默认)
  • REPEATABLE READ:可重复读(MySQL默认)
  • SERIALIZABLE:串行化(最高)

Q25: 数据库索引的作用和原理?

回答要点:

作用:

  • 加快查询速度
  • 保证数据唯一性(唯一索引)
  • 加速表连接

原理:

  • B+树:MySQL InnoDB默认索引结构
  • 哈希索引:Memory引擎使用

索引类型:

  • 主键索引:PRIMARY KEY
  • 唯一索引:UNIQUE
  • 普通索引:INDEX
  • 组合索引:多个字段组合

注意事项:

  • 索引不是越多越好(影响写入性能)
  • 避免在WHERE子句中使用函数
  • 最左前缀原则

Spring框架

Q26: Spring的IoC和DI是什么?

回答要点:

IoC(Inversion of Control,控制反转):

  • 传统方式:对象自己创建依赖
  • IoC方式:由容器创建和管理对象
  • 好处:降低耦合,提高灵活性

DI(Dependency Injection,依赖注入):

  • IoC的实现方式
  • 通过构造函数、setter方法、接口注入依赖

代码示例:

// 传统方式(紧耦合)
class UserService {
private UserDao userDao = new UserDaoImpl(); // 自己创建
}

// Spring方式(松耦合)
class UserService {
@Autowired
private UserDao userDao; // 容器注入
}

Q27: Spring Bean的作用域有哪些?

回答要点:

作用域 说明 使用场景
singleton 单例(默认) 无状态Bean
prototype 多例 有状态Bean
request 每个HTTP请求一个实例 Web应用
session 每个HTTP Session一个实例 Web应用
application 每个ServletContext一个实例 Web应用

代码示例:

@Component
@Scope("prototype") // 多例
public class MyBean {
// ...
}

Q28: Spring AOP是什么?有哪些应用场景?

回答要点:

AOP(Aspect-Oriented Programming,面向切面编程):

  • 将横切关注点(日志、事务、安全等)从业务逻辑中分离
  • 通过代理模式实现

核心概念:

  • Aspect(切面):横切关注点的模块化
  • Join Point(连接点):方法执行点
  • Pointcut(切点):匹配连接点的表达式
  • Advice(通知):在切点执行的动作
  • Target(目标对象):被代理的对象

应用场景:

  • 日志记录
  • 事务管理
  • 权限控制
  • 性能监控
  • 异常处理

代码示例:

@Aspect
@Component
public class LogAspect {

@Before("execution(* com.example.service.*.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("方法执行前:" + joinPoint.getSignature());
}

@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))",
returning = "result")
public void afterReturning(Object result) {
System.out.println("方法返回:" + result);
}
}

Q29: Spring MVC的工作流程?

回答要点:

1. 用户请求 → DispatcherServlet(前端控制器)

2. DispatcherServlet → HandlerMapping(处理器映射器)
查找对应的Controller

3. DispatcherServlet → HandlerAdapter(处理器适配器)
调用Controller方法

4. Controller处理请求,返回ModelAndView

5. DispatcherServlet → ViewResolver(视图解析器)
解析视图名称

6. 渲染视图,返回响应给用户

核心组件:

  • DispatcherServlet:前端控制器
  • HandlerMapping:映射请求到处理器
  • HandlerAdapter:调用处理器
  • ViewResolver:解析视图
  • Controller:处理业务逻辑

项目经验与场景题

Q30: 请介绍一下你做的项目?

回答模板:

项目背景:
- 项目名称、用途、开发时间
- 你在项目中的角色和职责

技术栈:
- 后端:Spring Boot、MyBatis、MySQL、Redis等
- 前端:Vue.js、Element UI等
- 工具:Git、Maven、IDEA等

核心功能:
- 功能1:具体实现和难点
- 功能2:具体实现和难点
- ...

遇到的难点和解决方案:
- 问题1:描述问题 → 分析原因 → 解决方案 → 效果
- 问题2:...

项目收获:
- 技术提升
- 团队协作
- 问题解决能力

示例回答:

我参与开发了一个在线图书管理系统,主要负责后端开发,项目周期3个月。

技术栈方面,后端使用Spring Boot + MyBatis + MySQL,缓存使用Redis,
消息队列使用RabbitMQ,部署使用Docker。

核心功能包括用户管理、图书管理、借阅管理等。我主要负责借阅模块的开发。

遇到的难点是并发借阅时的库存控制问题。我使用了Redis分布式锁来解决,
通过SETNX命令实现,确保同一本书在同一时刻只能被一个用户借阅。
最终成功解决了并发问题,系统在高并发下也能保证数据一致性。

通过这个项目,我深入理解了Spring Boot框架,学会了使用Redis解决
分布式场景下的并发问题,也提升了代码设计和问题解决能力。

Q31: 如何解决高并发问题?

回答要点:

1. 缓存

  • Redis缓存热点数据
  • 减少数据库压力

2. 消息队列

  • 异步处理,削峰填谷
  • RabbitMQ、Kafka等

3. 数据库优化

  • 读写分离
  • 分库分表
  • 索引优化

4. 限流

  • 令牌桶、漏桶算法
  • 使用Guava RateLimiter或Sentinel

5. 分布式锁

  • Redis分布式锁
  • Zookeeper分布式锁

6. CDN

  • 静态资源CDN加速

Q32: 如何保证接口的幂等性?

回答要点:

幂等性:同一请求执行多次,结果相同

实现方式:

1. 唯一索引

-- 数据库唯一索引防止重复插入
CREATE UNIQUE INDEX idx_order_no ON orders(order_no);

2. 分布式锁

String lockKey = "order:" + orderNo;
if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) {
try {
// 处理业务逻辑
} finally {
redisTemplate.delete(lockKey);
}
}

3. Token机制

// 1. 生成token,存入Redis
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("token:" + token, "1", 5, TimeUnit.MINUTES);

// 2. 请求时携带token
// 3. 验证token,使用后删除

4. 状态机

  • 通过状态流转保证幂等性

算法与数据结构

Q33: 常见的排序算法及其时间复杂度?

回答要点:

算法 平均时间复杂度 最坏时间复杂度 空间复杂度 稳定性
冒泡排序 O(n²) O(n²) O(1) 稳定
选择排序 O(n²) O(n²) O(1) 不稳定
插入排序 O(n²) O(n²) O(1) 稳定
快速排序 O(n log n) O(n²) O(log n) 不稳定
归并排序 O(n log n) O(n log n) O(n) 稳定
堆排序 O(n log n) O(n log n) O(1) 不稳定

快速排序代码:

public void quickSort(int[] arr, int left, int right) {
if (left < right) {
int pivot = partition(arr, left, right);
quickSort(arr, left, pivot - 1);
quickSort(arr, pivot + 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;
}

Q34: 如何判断链表是否有环?

回答要点:

方法1:快慢指针(Floyd判圈算法)

public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}

ListNode slow = head;
ListNode fast = head.next;

while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}

方法2:HashSet

public boolean hasCycle(ListNode head) {
Set<ListNode> seen = new HashSet<>();
while (head != null) {
if (seen.contains(head)) {
return true;
}
seen.add(head);
head = head.next;
}
return false;
}

其他技术栈

Q35: Redis的常用数据类型?

回答要点:

类型 说明 应用场景
String 字符串 缓存、计数器
Hash 哈希表 存储对象
List 列表 消息队列、最新列表
Set 集合 去重、交集并集
Sorted Set 有序集合 排行榜
Bitmap 位图 签到、布隆过滤器
HyperLogLog 基数统计 UV统计

Q36: HTTP和HTTPS的区别?

回答要点:

特性 HTTP HTTPS
端口 80 443
安全性 明文传输 加密传输
证书 不需要 需要SSL证书
性能 稍慢(加密开销)
SEO 一般 更好

HTTPS工作原理:

  1. 客户端请求HTTPS连接
  2. 服务器返回SSL证书
  3. 客户端验证证书
  4. 双方协商加密算法和密钥
  5. 使用对称加密传输数据

Q37: RESTful API设计原则?

回答要点:

  • 资源:使用名词,如 /users/orders
  • HTTP方法
    • GET:查询
    • POST:创建
    • PUT:更新(完整)
    • PATCH:更新(部分)
    • DELETE:删除
  • 状态码:200、201、400、404、500等
  • 版本控制/api/v1/users
  • 过滤和分页/users?page=1&size=10

示例:

GET    /api/v1/users          # 获取用户列表
GET /api/v1/users/1 # 获取ID为1的用户
POST /api/v1/users # 创建用户
PUT /api/v1/users/1 # 更新用户1
DELETE /api/v1/users/1 # 删除用户1

面试技巧总结

回答问题的STAR法则

  • S(Situation):情境
  • T(Task):任务
  • A(Action):行动
  • R(Result):结果

常见问题准备

  1. 自我介绍(1-2分钟)

    • 姓名、学校、专业
    • 技术栈和项目经验
    • 为什么选择这个岗位
  2. 为什么选择我们公司?

    • 了解公司业务和技术
    • 表达对岗位的兴趣
    • 展示学习意愿
  3. 你的优缺点?

    • 优点:结合技术能力
    • 缺点:说可以改进的,并说明改进计划
  4. 职业规划?

    • 短期:快速成长,掌握核心技术
    • 长期:成为技术专家或架构师

技术问题回答技巧

  1. 先回答核心概念
  2. 举例说明
  3. 对比其他方案
  4. 结合实际项目经验

注意事项

  • 诚实:不会的就说不会,但可以表达学习意愿
  • 自信:保持自信但不要自负
  • 沟通:思路清晰,表达流畅
  • 准备:提前了解公司和岗位要求

推荐学习资源

  1. 书籍

    • 《Java核心技术》
    • 《深入理解Java虚拟机》
    • 《Spring实战》
    • 《Redis设计与实现》
  2. 在线资源

    • Java官方文档
    • Spring官方文档
    • 牛客网、LeetCode(算法练习)
    • GitHub(开源项目学习)
  3. 实践项目

    • 个人博客系统
    • 在线商城
    • 图书管理系统
    • 在线聊天系统

祝面试顺利!记住:准备充分,自信表达,诚实回答!