11.2 TCL面试准备
JUC
线程池
7个参数
线程状态
jstack 输出中常见的线程状态:
NEW
RUNNABLE:线程正在执行
BLOCKED:线程被阻塞,等待获取锁
WAITING:线程在等待另一个线程执行特定操作
TIMED_WAITING:线程在等待一个指定时间间隔
TERMINATED:线程已退出
volatile
lock前缀,效果相当于内存屏障StoreLoad,防止指令重排序
JVM
类加载:加载,链接(验证,准备,解析),初始化,使用,卸载
创建对象过程:类加载检查、分配内存、初始化零值、必要设置(对象头)、执行 init 方法
类加载器:启动类加载器、扩展类加载器、应用程序类加载器
MySQL
事务隔离级别 – 可避免
读未提交
读已提交 – 脏读
可重复读(默认)– 脏读,不可重复读
串行化 – 脏读,不可重复读,幻读

索引失效场景:
不满足最左匹配函数(a = ‘123’ 隐式转换也算)或表达式 (a + 1 = 5)not in 和 !=,这两个走全表扫描,in不会失效模糊匹配 LIKE 以 % 开头,不以%开头可以走索引?可以走索引,前缀匹配
索引下推(ICP):
MySQL 5.6+ 优化:在索引层过滤数据,减少回表次数。
例如:WHERE a=1 AND b LIKE ‘%2’,索引先过滤 a=1,再用索引过滤 b。
explain: type(ALL ref) 、key(用了哪些索引)、 rows(扫描的行数)
例子:EXPLAIN SELECT * FROM table WHERE id=1;
汇总问题:
自我介绍介绍项目(实验室研发项目、实习项目、核心参与项目等)synchronized 相关知识(含锁优化)事务是什么(含 ACID、存储引擎关联)InnoDB 存储引擎相关(含与 MyISAM 的区别)单例模式 DCL(含 volatile 使用原因及底层)常用的幂等方案有哪些除数据库、中间件外,还有哪些地方可以做幂等Stream 流有哪些常用方法场景题:将含 id 和名字的对象数组通过 Stream 流转换成 Map对 JVM 的了解(含内存区域、GC 相关)JVM 内存六大区域Minor GC 与 Full GC 相关知识触发 Full GC 会怎么样左连接与右连接的区别(含深层逻辑)场景题:给定两个表,如何设计左右连接(无字段/效果说明时的思路)有没有优化 SQL 的经验?优化思路及达到的效果MySQL 的 explain 如何使用LinkedList 和 ArrayList 的区别及使用场景如何设计一张表的各个字段设计生产者消费者模式场景题:账户有转入转出功能,如何设计保证安全实习/项目所用的系统架构及权限认证方案实习中学到了什么(含全局角度相关)AI 相关项目介绍及落地场景网络分层模型(含 OSI 七层模型、网卡所属层级)发送请求经过网络七层模型到达目的地的详细过程什么是操作系统对 Java 的理解抽象类和接口的区别Spring MVC 的请求过程发送请求经过网络到达项目的处理流程及原理Struts 相关问题常用的 Web 容器(含 Tomcat 及之前的容器)你做过的比较成功的事是什么在校期间参加过什么活动方法重载的返回值必须一样吗构造方法在继承中的特点抽象类里面只能有抽象方法吗接口和抽象类的共同点和区别C# 和 Java 的区别与共同点大学期间学的比较好的课程及相关问题最常用的数据结构是什么?在项目中哪里用到?具体说明及源码了解情况项目中遇到过什么问题?如何解决?调试时多个线程影响代码,如何确定具体是哪个线程的问题TCP 和 UDP 的区别HTTPS 和 HTTP 的区别及两者的工作流程有哪些加密算法有哪些缓存策略写过哪些博客?主要学习方式是什么epoll 和 select 的区别,如何进行压测如何判断一个链表是否有环三次握手与四次挥手相关知识线程和进程的区别CPU 如何分配进程跳表相关知识是否用过 k8s、opencv为什么选择后端开发学习 Java 的流程计划对 DDD 架构的理解及劣势多线程如何使用大数据量情况下如何迁移数据和添加索引数据库表字段设计(结合具体功能逻辑说明)线程池的核心参数、拒绝策略、并发执行时的执行流程及种类线程和底层操作系统的关系并发线程在多核 CPU 时是否每个核心都会用到?单核 CPU 情况下如何执行等待队列的种类线程池任务执行完后如何后续处理最左前缀法则和索引下推相关知识Raft 算法及与其他算法的对比科研方向介绍作为负责人如何安排项目推进需求开发冲突如何解决Linux 如何查看文件?如何根据关键词搜索文件wait() 和 sleep() 的区别?sleep(0) 有什么用MySQL 常见隔离级别、默认级别、场景判断及底层引擎区别如何保证多线程开发的安全性Java IO 流用过哪些?如果不关闭文件会有什么后果Redis 常见数据类型、用途及是否搭建过 Redis 集群String 可不可以被继承?为什么MySQL 中使用 in 会不会造成索引失效?为什么Redis 的持久化方式有哪些?有什么区别对面试公司/部门的疑问(如部门职能、面试流程等)
答案:
自我介绍
答:自我介绍需突出与 Java 后端相关的核心竞争力,结构建议为“个人基本信息→技术栈储备→项目/实习经历→岗位匹配度”。基本信息含姓名、学校、专业、求职方向;技术栈明确 Java 核心(集合、JVM、多线程)、框架(Spring、MyBatis 等)、数据库(MySQL、Redis)、中间件等;项目/实习经历用“技术+场景+成果”简要说明;最后强调对后端开发的兴趣及与岗位的契合点,时长控制在 1-2 分钟。
介绍项目(实验室研发项目、实习项目、核心参与项目等)
答:按“项目背景→个人职责→技术选型→核心难点及解决方案→成果/优化点”逻辑介绍。背景说明项目解决的问题;职责明确自己负责的模块(如接口开发、性能优化);技术选型讲清用的框架、数据库等及选择原因;难点聚焦 1-2 个核心问题(如高并发、数据一致性),说明用的技术方案(如锁机制、缓存策略);成果用量化数据体现(如接口响应时间从 500ms 优化至 100ms),避免泛泛而谈。
synchronized 相关知识(含锁优化)
答:synchronized 是 Java 内置锁,保证多线程并发安全,可修饰方法、代码块,核心原理是通过监视器锁(Monitor)实现互斥。锁优化是 JVM 对 synchronized 的性能提升手段,流程为:1. 偏向锁:单线程场景下,避免锁竞争开销,仅记录线程 ID,无 CAS 操作;2. 轻量级锁:多线程交替执行时,用 CAS 尝试获取锁,无需阻塞线程;3. 重量级锁:多线程竞争激烈时,膨胀为操作系统级互斥锁,线程会阻塞,开销最大。锁优化通过“膨胀”机制,根据竞争程度动态切换锁状态,平衡性能与安全性。
事务是什么(含 ACID、存储引擎关联)
答:事务是数据库中一组不可分割的操作集合,要么全部执行成功,要么全部执行失败,用于保证数据一致性。核心特性 ACID:原子性(操作不可拆分)、一致性(执行前后数据符合业务规则)、隔离性(多事务并发时互不干扰)、持久性(执行成功后数据永久保存)。事务支持与存储引擎相关,InnoDB 支持事务(通过 redo log、undo log 实现原子性和持久性),MyISAM 不支持事务,适用于读多写少、无需事务的场景。
InnoDB 存储引擎相关(含与 MyISAM 的区别)
答:InnoDB 是 MySQL 默认存储引擎,支持事务、行级锁、外键,适合高并发、数据一致性要求高的场景。与 MyISAM 的核心区别:1. 事务支持:InnoDB 支持,MyISAM 不支持;2. 锁粒度:InnoDB 行级锁(并发性能好),MyISAM 表级锁(并发性能差);3. 存储结构:InnoDB 有聚簇索引(数据与索引存储在一起),MyISAM 非聚簇索引(数据与索引分离);4. 崩溃恢复:InnoDB 支持(通过 redo/undo log),MyISAM 不支持;5. 外键:InnoDB 支持,MyISAM 不支持。
单例模式 DCL(含 volatile 使用原因及底层)
答:DCL(双重检查锁定)是单例模式的一种实现方式,核心代码为“private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }”。volatile 必须加,原因:instance = new Singleton() 分三步(分配内存、初始化对象、赋值引用),JVM 可能重排序,导致线程获取到未初始化的对象;volatile 禁止指令重排序,保证三步按顺序执行,同时保证内存可见性(一个线程修改后,其他线程立即可见)。底层通过内存屏障实现:写操作后加 StoreLoad 屏障,阻止之前的指令重排序到写操作之后。
常用的幂等方案有哪些
答:幂等指同一请求多次执行结果一致,常用方案:1. 数据库层面:唯一索引(防止重复插入)、乐观锁(加 version 字段,更新时判断版本)、悲观锁(select for update 锁定数据);2. 中间件层面:消息队列去重(如 RocketMQ 的 MessageID 去重)、缓存去重(缓存请求 ID,已处理则直接返回结果);3. 应用层面:请求 ID 去重(生成唯一 requestId,服务端记录已处理的 ID)、状态机控制(如订单状态从“待支付”只能转为“已支付”或“已取消”,避免重复操作)。
除数据库、中间件外,还有哪些地方可以做幂等
答:主要在应用层实现,核心方案:1. 接口层:网关层拦截(如 Nginx 记录请求 ID,重复请求直接返回)、接口参数校验(结合业务规则判断是否重复,如订单号唯一);2. 业务层:状态机控制(通过状态流转约束,避免重复操作)、本地缓存去重(如用 ConcurrentHashMap 存储已处理的请求 ID,查询成本低);3. 客户端层面:请求发送后禁用按钮、设置请求超时重试策略(避免因超时重复发送)。
Stream 流有哪些常用方法
答:Stream 流用于集合数据处理,分中间操作(返回 Stream,可链式调用)和终止操作(返回非 Stream,触发流处理)。常用中间操作:filter(过滤元素)、map(元素转换)、flatMap(扁平化处理,如集合嵌套集合)、sorted(排序)、distinct(去重)、limit(限制返回数量);常用终止操作:forEach(遍历元素)、collect(收集结果到集合/Map)、count(统计数量)、anyMatch(任意元素满足条件)、allMatch(所有元素满足条件)、findFirst(获取第一个元素)、reduce(归约计算,如求和)。
场景题:将含 id 和名字的对象数组通过 Stream 流转换成 Map
答:假设对象类为 User(含 Integer id、String name 字段及 getter 方法),数组为 User[] users,转换代码:Map<Integer, String> userMap = Arrays.stream(users).collect(Collectors.toMap(User::getId, User::getName, (k1, k2) -> k1));。核心说明:1. Arrays.stream(users) 将数组转为 Stream;2. Collectors.toMap 接收三个参数,前两个为 key 和 value 的映射函数(通过方法引用获取 id 和 name);3. 第三个参数为合并函数,解决 key 重复冲突(此处保留第一个值),若确定 id 唯一可省略,但建议添加避免异常。
对 JVM 的了解(含内存区域、GC 相关)
答:JVM 是 Java 程序的运行环境,负责将字节码转为机器指令。核心内容:1. 内存区域:分为方法区、堆、程序计数器、虚拟机栈、本地方法栈、直接内存(六大区域),堆和方法区为线程共享,其余为线程私有;2. 类加载机制:通过类加载器( Bootstrap、Extension、Application)实现“加载(.class -> Class对象)、验证、准备(静态字段初始化)、解析(符号引用 -> 直接引用)、初始化(程序类加载器)”五步骤;3. GC 相关:垃圾回收针对堆和方法区,核心是判断对象是否存活(可达性分析),常用回收算法(标记-清除、标记-复制、标记-整理),及垃圾收集器(如 Serial、Parallel、G1);4. 内存模型:通过 volatile、synchronized 保证可见性、原子性、有序性。
JVM 内存六大区域
答:1. 程序计数器:线程私有,存储当前线程执行的字节码行号,无内存溢出;2. 虚拟机栈:线程私有,存储方法调用的栈帧(含局部变量表、操作数栈等),栈深度过大抛 StackOverflowError,内存不足抛 OutOfMemoryError;3. 本地方法栈:线程私有,为 Native 方法提供内存空间,异常类型同虚拟机栈;4. 堆:线程共享,存储对象实例和数组,是 GC 主要区域,内存不足抛 OutOfMemoryError;5. 方法区:线程共享,存储类信息、常量、静态变量等,JDK 8 后用元空间实现(基于本地内存),JDK 7 及之前用永久代,内存不足抛 OutOfMemoryError;6. 直接内存:非 JVM 运行时数据区,通过 Unsafe 类分配,不受堆大小限制,内存不足抛 OutOfMemoryError。
Minor GC 与 Full GC 相关知识
答:Minor GC 针对 JVM 年轻代(Eden 区、Survivor
Minor GC 与 Full GC 相关知识
答:Minor GC 针对 JVM 年轻代(Eden 区、Survivor 区)的垃圾回收,Full GC(又称 Major GC)针对老年代+年轻代(部分收集器会包含方法区)的垃圾回收,核心区别如下:
触发条件:
Minor GC:Eden 区满时触发,Survivor 区满不会直接触发,会通过复制算法转移对象;Full GC:老年代满、方法区(元空间)满、调用 System.gc()(建议不主动调用)、年轻代晋升到老年代的对象大小超过老年代剩余空间。
回收流程:
Minor GC:采用“标记-复制”算法,将 Eden 区和一个 Survivor 区(From)存活的对象复制到另一个 Survivor 区(To),清空 Eden 和 From 区,对象年龄+1,年龄达到阈值(默认15)则晋升老年代;Full GC:采用“标记-清除”或“标记-整理”算法,先标记老年代存活对象,再清除或整理碎片,回收效率远低于 Minor GC。
影响:Minor GC 停顿时间短(年轻代对象生命周期短,存活少),Full GC 停顿时间长(老年代对象存活多),应尽量避免频繁 Full GC。
触发 Full GC 会怎么样
答:触发 Full GC 后会产生以下影响,核心是“性能损耗+业务阻塞风险”:
STW(Stop The World):大部分收集器执行 Full GC 时会暂停所有用户线程,直到回收完成,停顿时间可能达百毫秒级,高并发场景下会导致接口响应超时;
资源占用:GC 线程会占用 CPU 资源,导致业务线程资源竞争加剧,系统吞吐量下降;
内存碎片整理:若采用“标记-整理”算法,会整理老年代内存碎片,优化内存分配效率,但会增加停顿时间;若用“标记-清除”算法,会产生碎片,可能导致后续大对象无法分配内存,触发连续 Full GC;
系统告警:频繁 Full GC 可能触发监控告警,提示内存泄漏或配置不合理(如老年代过小);
优化方向:排查内存泄漏(如未关闭连接、静态集合缓存过大)、调整 JVM 参数(增大老年代空间、调整晋升阈值)、优化对象创建(减少大对象、复用对象)。
左连接与右连接的区别(含深层逻辑)
答:核心是“匹配规则+结果集包含范围”,基于两个表 A(左表)和 B(右表)说明:
表面区别:
左连接(LEFT JOIN):返回左表所有记录,右表匹配上的记录显示对应字段,未匹配上的显示 NULL;右连接(RIGHT JOIN):返回右表所有记录,左表匹配上的记录显示对应字段,未匹配上的显示 NULL。
深层逻辑:
匹配机制:连接的核心是“关联条件”(如 A.id = B.a_id),左连接优先保证左表完整性,右连接优先保证右表完整性;NULL 处理:未匹配的字段值为 NULL,若查询中使用 NULL 敏感的函数(如 COUNT、SUM)会影响结果(如 COUNT(B.id) 会忽略 NULL,COUNT(*) 包含 NULL);性能影响:左连接中若左表数据量远大于右表,需确保右表关联字段有索引;右连接同理,否则会触发全表笛卡尔积,性能极差;等价转换:LEFT JOIN A B 等价于 RIGHT JOIN B A,实际开发中左连接更常用,可读性更强。
场景题:给定两个表,如何设计左右连接(无字段/效果说明时的思路)
答:核心是“先明确业务目标,再补全关联条件”,步骤如下:
询问业务场景:明确查询目的(如“查询所有用户及关联订单”“查询所有订单及下单用户”),确定需保留的表(左表/右表);
确认表结构:询问两个表的核心字段(如用户表 user(id, name)、订单表 order(id, user_id, amount)),找到关联字段(如 user.id = order.user_id);
定义连接规则:
若需保留“用户表所有数据”:用左连接(SELECT u., o. FROM user u LEFT JOIN order o ON u.id = o.user_id);若需保留“订单表所有数据”:用右连接(SELECT u., o. FROM user u RIGHT JOIN order o ON u.id = o.user_id);
处理特殊情况:若存在多对多关系(如用户-角色表),需先通过中间表关联,再用左右连接保留目标表数据;若无需保留 NULL 记录,可加 WHERE o.id IS NOT NULL 转为内连接。
有没有优化 SQL 的经验?优化思路及达到的效果
答:优化核心是“减少数据扫描范围、降低锁竞争、提升索引效率”,结合实际场景说明:
优化思路(分步骤):
需求分析:明确查询目的,删除不必要的字段(如不用 SELECT *,只查需要的列)和条件;索引优化:给 WHERE 条件、JOIN 关联字段、ORDER BY 字段建立索引(避免过度索引),遵循最左前缀法则;SQL 语法优化:避免使用 OR(用 UNION 替代)、IN(数据量大时用 EXISTS 替代)、函数操作索引字段(如 WHERE DATE(create_time) = ‘2024-01-01’ 会导致索引失效);表结构优化:拆分大表(如将订单表按时间分表)、增加冗余字段减少 JOIN、用 tinyint 替代 int 节省空间;执行计划分析:用 EXPLAIN 查看 SQL 执行计划,优化 type(从 ALL 转为 ref/range)、key(确保使用索引)、rows(减少扫描行数);
效果示例:优化前查询“近3个月用户订单”耗时 800ms,通过添加 create_time 索引+分表查询,耗时降至 50ms,吞吐量提升 15 倍。
MySQL 的 explain 如何使用
答:EXPLAIN 用于分析 SQL 执行计划,判断是否使用索引、扫描行数等,用法和核心字段如下:
使用方式:在 SQL 语句前加 EXPLAIN,如 EXPLAIN SELECT * FROM user WHERE id = 1;核心字段解读:
id:查询执行顺序,id 相同按从上到下执行,id 越大优先级越高;select_type:查询类型(SIMPLE 简单查询、SUBQUERY 子查询、JOIN 连接查询等);table:当前查询的表;type:访问类型(性能从好到差:system > const > eq_ref > ref > range > ALL),目标是避免 ALL(全表扫描);possible_keys:可能使用的索引;key:实际使用的索引(为 NULL 表示未使用索引);key_len:索引使用的长度(越长说明使用的索引字段越全);rows:预估扫描的行数(越少越好);Extra:额外信息(Using index 覆盖索引、Using where 过滤条件、Using filesort 文件排序(需优化)、Using temporary 临时表(需优化))。
优化依据:若 type 为 ALL、key 为 NULL,需检查是否缺少索引;若 Extra 有 Using filesort/Using temporary,需优化 ORDER BY 或 GROUP BY 字段,确保使用索引。
LinkedList 和 ArrayList 的区别及使用场景
答:核心区别在于底层数据结构,进而影响操作性能:
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组(基于数组实现) | 双向链表(基于节点引用实现) |
| 访问元素(get/set) | O(1)(直接通过索引访问) | O(n)(需遍历链表) |
| 新增/删除元素 | O(n)(中间位置需移动元素) | O(1)(已知节点位置,只需修改引用) |
| 内存占用 | 连续内存,可能有冗余空间(扩容) | 非连续内存,每个节点含数据+前后引用 |
| 线程安全 | 非线程安全 | 非线程安全 |
| 扩容机制 | 容量不足时扩容为原来的1.5倍 | 无扩容机制,按需添加节点 |
使用场景:
ArrayList:适合“查询频繁、增删少”的场景(如商品列表展示、数据查询缓存);LinkedList:适合“增删频繁、查询少”的场景(如队列、栈实现、订单状态流转列表);
注意:若需线程安全,可使用 Collections.synchronizedList() 包装,或用 CopyOnWriteArrayList(ArrayList 线程安全版)。
如何设计一张表的各个字段
答:表设计核心是“满足业务需求+数据完整性+性能优化”,步骤如下:
明确业务场景:确定表的用途(如订单表、用户表),梳理核心业务字段(如订单表需包含订单号、用户ID、金额、状态等);
字段类型选择:遵循“最小够用”原则,如:
布尔值用 tinyint(1)(0/1);金额用 decimal(10,2)(避免浮点精度问题);时间用 datetime(精确到秒)或 timestamp(自动同步时区);字符串用 varchar(变长,节省空间),固定长度用 char(如手机号、身份证号);
约束设计:
主键(PK):唯一标识记录,优先用自增 int 或 UUID(分布式场景);外键(FK):关联其他表(如订单表 user_id 关联用户表 id),保证数据一致性;非空(NOT NULL):必填字段(如订单号、金额),避免 NULL 处理麻烦;唯一索引(UNIQUE):避免重复数据(如手机号、邮箱);默认值(DEFAULT):可选字段设置默认值(如订单状态默认 0-待支付);
性能优化:
拆分大字段:如用户表的头像URL、简介可拆分到用户扩展表,避免查询主表时加载冗余数据;索引预留:给常用查询字段(如订单状态、创建时间)预留索引位,避免后续修改表结构;
示例(订单表):
CREATE TABLE `order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID(主键)',
`order_no` varchar(32) NOT NULL COMMENT '订单号(唯一索引)',
`user_id` bigint(20) NOT NULL COMMENT '用户ID(外键关联user表id)',
`amount` decimal(10,2) NOT NULL COMMENT '订单金额',
`status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '订单状态:0-待支付,1-已支付,2-已取消',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
设计生产者消费者模式
答:生产者消费者模式用于“解耦生产者(生成数据)和消费者(处理数据)”,平衡生产和消费速度,核心是“阻塞队列+线程协作”,Java 实现方案如下:
方案1:基于 BlockingQueue(推荐,JDK 自带,线程安全)
定义任务类:如 Task(含任务ID、数据内容);生产者类:实现 Runnable,向 BlockingQueue 中添加任务,队列满时阻塞;消费者类:实现 Runnable,从 BlockingQueue 中获取任务,队列空时阻塞;测试代码:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
// 任务类
class Task {
private int id;
private String data;
// 构造器、getter
}
// 生产者
class Producer implements Runnable {
private BlockingQueue<Task> queue;
public Producer(BlockingQueue<Task> queue) { this.queue = queue; }
@Override
public void run() {
try {
// 生成任务并放入队列
for (int i = 0; i < 10; i++) {
Task task = new Task(i, "数据" + i);
queue.put(task); // 队列满时阻塞
System.out.println("生产者生产任务:" + i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 消费者
class Consumer implements Runnable {
private BlockingQueue<Task> queue;
public Consumer(BlockingQueue<Task> queue) { this.queue = queue; }
@Override
public void run() {
try {
while (true) {
Task task = queue.take(); // 队列空时阻塞
System.out.println("消费者处理任务:" + task.getId());
// 模拟处理耗时
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 测试
public class ProducerConsumerDemo {
public static void main(String[] args) {
BlockingQueue<Task> queue = new ArrayBlockingQueue<>(5); // 队列容量5
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
核心说明:BlockingQueue 的 put()(生产者)和 take()(消费者)方法自带阻塞机制,无需手动处理线程唤醒/等待,线程安全且简洁;其他方案:基于 Object 的 wait()/notify() 实现(需手动处理队列空/满判断,易出错)、基于线程池+消息队列(如 RabbitMQ,分布式场景使用)。
场景题:账户有转入转出功能,如何设计保证安全
答:核心是“数据一致性+并发安全+幂等性”,设计方案如下:
表结构设计(账户表+交易记录表):
-- 账户表(核心字段)
CREATE TABLE `account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '账户ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`balance` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额',
`version` int(11) NOT NULL DEFAULT 0 COMMENT '版本号(乐观锁)',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 交易记录表(用于对账和幂等)
CREATE TABLE `account_transaction` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '交易ID',
`trans_no` varchar(32) NOT NULL COMMENT '交易流水号(唯一)',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`amount` decimal(10,2) NOT NULL COMMENT '交易金额(正数转入,负数转出)',
`balance_after` decimal(10,2) NOT NULL COMMENT '交易后余额',
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '交易状态:1-成功,0-失败',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '交易时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_trans_no` (`trans_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
并发安全保障:
乐观锁:通过 account 表的 version 字段,更新余额时判断版本号(),避免并发更新覆盖;事务:转账操作(如 A 转 B)用事务包裹,确保 A 扣款和 B 到账原子性(要么都成功,要么都失败);
UPDATE account SET balance = balance + ?, version = version + 1 WHERE user_id = ? AND version = ?
幂等性保障:
生成唯一交易流水号(trans_no),每次转账请求携带,交易前检查流水号是否已存在,避免重复转账;
业务逻辑约束:
转出时校验余额是否充足();金额字段用 decimal 类型,避免浮点精度误差;
balance >= 转出金额
异常处理:
捕获事务异常,回滚操作并记录交易状态为失败;定时任务对账(账户余额 = 所有交易金额之和),发现不一致及时告警。
实习/项目所用的系统架构及权限认证方案
答:以“微服务架构”和“Spring Security + JWT”为例说明,可根据实际项目调整:
系统架构:
架构模式:微服务架构(拆分用户服务、订单服务、支付服务等);核心组件:
注册中心:Nacos/Eureka(服务注册与发现);网关:Spring Cloud Gateway(路由转发、统一认证);通信:OpenFeign(服务间HTTP调用);配置中心:Nacos(统一配置管理);熔断降级:Sentinel(避免服务雪崩);数据库:MySQL(业务数据)+ Redis(缓存);
流程:用户请求 → 网关(认证、路由) → 对应微服务 → 数据库/缓存;
权限认证方案(Spring Security + JWT):
认证流程:
用户登录(输入账号密码) → 服务端校验 → 生成 JWT 令牌(含用户ID、角色) → 返回给客户端;后续请求:客户端在请求头携带 JWT → 网关拦截校验令牌有效性 → 解析用户信息 → 放行到对应服务;
授权控制:
基于角色(RBAC):给用户分配角色(如 ADMIN、USER),服务端通过 控制接口访问权限;资源权限:细粒度控制(如用户只能查看自己的订单),通过数据库存储用户-资源关联关系,接口层校验;
@PreAuthorize("hasRole('ADMIN')")
安全优化:
JWT 过期时间设置(如 2 小时),配合刷新令牌机制;令牌加密签名(避免篡改);敏感接口加验证码/二次校验(如转账、修改密码)。
实习中学到了什么(含全局角度相关)
答:需结合实习内容,突出“技术能力+思维提升”,尤其是全局视角,示例如下:
技术层面:掌握了 Spring Boot/Cloud 微服务开发、MySQL 性能优化、Redis 缓存使用,解决了实际问题(如用 Redis 解决接口重复提交、用索引优化慢查询);
流程层面:了解了企业级项目的开发流程(需求评审→开发→测试→上线),学会用 Git 进行版本控制、JIRA 跟踪任务、Jenkins 部署项目;
全局视角:
不再只关注单个接口实现,而是考虑系统可用性(如熔断降级避免服务雪崩)、可扩展性(如设计通用接口便于后续迭代)、数据一致性(如分布式事务处理);理解了“需求→技术方案”的转化逻辑,比如面对高并发场景,会从“缓存、异步、分库分表”等多个维度设计方案,而非仅优化代码;
协作层面:学会与产品、测试、运维沟通,明确需求边界,配合排查线上问题(如通过日志定位接口超时原因)。
AI 相关项目介绍及落地场景
答:按“项目背景→技术方案→落地价值”介绍,落地场景结合业务实际:
项目介绍:例如“基于 Transformer 的文本分类系统”
背景:解决企业客服工单自动分类问题(人工分类效率低、误差大);技术方案:用 BERT 预训练模型微调,输入工单文本,输出分类标签(如“咨询”“投诉”“售后”);成果:分类准确率达 92%,减少 60% 人工分类工作量;
落地场景:
企业客服:自动分流工单,咨询类转智能机器人,投诉类转人工专员;内容审核:社交平台自动识别违规文本(如广告、辱骂),降低审核成本;智能检索:电商平台根据用户咨询文本,匹配相关商品或售后政策;数据分析:统计不同类型工单占比,为产品优化提供数据支撑(如投诉集中在物流,可优化物流合作商);
延伸:落地时需考虑“性能(响应时间控制在 100ms 内)、部署成本(用 Docker 容器化部署)、迭代优化(持续收集新数据微调模型)”。
网络分层模型(含 OSI 七层模型、网卡所属层级)
答:网络分层模型用于规范网络设备通信,核心是 OSI 七层模型和 TCP/IP 四层模型:
OSI 七层模型(从下到上):
物理层:传输比特流(0/1),硬件设备(网线、网卡、集线器),网卡属于物理层;数据链路层:将比特流封装为帧,处理物理地址(MAC 地址),设备(交换机、网桥);网络层:实现跨网段路由转发,处理逻辑地址(IP 地址),设备(路由器);传输层:提供端到端通信,保障可靠性(TCP)或实时性(UDP),协议(TCP、UDP);会话层:建立、管理、终止会话(如 TCP 连接);表示层:数据格式转换(如加密、压缩、编码);应用层:提供应用程序接口,协议(HTTP、FTP、DNS);
TCP/IP 四层模型(实际使用):物理层+数据链路层(网络接口层)、网络层、传输层、应用层;核心作用:分层解耦,每一层只关注自身功能,通过接口与上下层交互,便于维护和扩展。
发送请求经过网络七层模型到达目的地的详细过程
答:以“浏览器访问 www.baidu.com”为例,过程如下(从应用层到物理层,再反向解析):
应用层(浏览器):用户输入 URL,浏览器解析为 HTTP 请求(含请求方法、路径、参数);
表示层:对 HTTP 数据进行编码(如 UTF-8),无需加密则直接传递;
会话层:建立 TCP 会话(后续传输层会完成三次握手);
传输层:
浏览器请求操作系统创建 TCP 连接,TCP 协议给 HTTP 数据分段,添加源端口(随机)和目标端口(80);
网络层:
IP 协议给 TCP 分段添加源 IP(本机 IP)和目标 IP(百度服务器 IP,通过 DNS 解析获取);路由器根据目标 IP 选择路由路径,转发数据包;
数据链路层:
给 IP 数据包添加源 MAC 地址(本机网卡 MAC)和目标 MAC 地址(下一跳设备 MAC,如路由器);数据包通过网卡转为比特流,在物理介质(网线/Wi-Fi)传输;
物理层:比特流通过网络设备传输到百度服务器所在网络;
反向过程(服务器响应):
百度服务器从物理层接收比特流,逐层解封装(数据链路层→网络层→传输层→应用层),处理 HTTP 请求后返回响应数据,按同样流程传输回浏览器,浏览器渲染页面。
什么是操作系统
答:操作系统(OS)是“管理计算机硬件与软件资源的系统软件”,核心作用是“作为用户与计算机硬件的接口”,关键功能如下:
进程管理:调度进程(程序的执行实例),分配 CPU 资源,实现进程并发执行;
内存管理:分配和回收内存,实现内存隔离(如进程地址空间独立),虚拟内存管理(扩大可用内存);
设备管理:管理硬件设备(如显卡、硬盘、网卡),通过设备驱动程序实现设备与内核通信;
文件管理:管理文件系统(如 NTFS、EXT4),提供文件的创建、读取、修改、删除接口;
用户接口:提供命令行(如 Linux Shell)、图形界面(如 Windows 桌面)、编程接口(如系统调用),方便用户操作;
常见操作系统:Windows(桌面)、Linux(服务器)、macOS(桌面/开发)、Android(移动设备)。
对 Java 的理解
答:Java 是“面向对象的跨平台编程语言”,核心特点和价值如下:
核心特性:
跨平台:通过 JVM(Java 虚拟机)实现“一次编写,到处运行”(Java 代码编译为字节码,JVM 解释字节码为机器指令);面向对象:封装、继承、多态,便于代码复用和扩展;安全性:内置安全机制(如字节码校验、沙箱机制),避免恶意代码执行;健壮性:自动垃圾回收(GC)避免内存泄漏,强类型检查减少类型错误;多线程:内置多线程支持(Thread 类、并发包),便于开发高并发程序;
技术生态:
开发框架:Spring、Spring Boot、MyBatis(后端开发);中间件:Dubbo(RPC)、RabbitMQ(消息队列)、Redis(缓存);应用场景:后端开发(微服务、分布式系统)、大数据(Hadoop、Spark)、Android 开发;
核心价值:平衡“开发效率、性能、可维护性”,适合企业级应用开发,生态完善,人才储备充足。
抽象类和接口的区别
答:核心区别在于“设计目的+使用场景”,具体对比如下:
| 特性 | 抽象类(abstract class) | 接口(interface) |
|---|---|---|
| 关键字 | abstract | interface |
| 继承/实现方式 | 子类用 extends 继承(单继承) | 类用 implements 实现(多实现) |
| 方法类型 | 可包含抽象方法、非抽象方法、静态方法(Java 8+) | 默认为抽象方法,Java 8+ 可含 default 方法、静态方法 |
| 成员变量 | 可包含任意类型变量(默认 default 修饰) | 只能是 public static final 常量(必须初始化) |
| 构造方法 | 有构造方法(供子类调用) | 无构造方法 |
| 设计目的 | 代码复用(子类共享父类方法/变量) | 行为约束(类需实现指定方法) |
使用场景:
抽象类:适合“有共性实现的场景”(如 Animal 抽象类,包含 eat() 非抽象方法,Dog、Cat 子类继承后可直接使用);接口:适合“无共性实现,仅需统一行为”(如 Runnable 接口,仅定义 run() 方法,线程类实现该接口即可被线程调度)。
Spring MVC 的请求过程
答:Spring MVC 是“基于 MVC 设计模式的请求驱动型框架”,核心是“DispatcherServlet(前端控制器)”,请求流程如下(10步):
用户发送 HTTP 请求,请求被 DispatcherServlet 拦截(web.xml 配置映射);DispatcherServlet 调用 HandlerMapping(处理器映射器),根据请求 URL 查找对应的 Handler(Controller 方法);HandlerMapping 返回 HandlerExecutionChain(含 Handler 和拦截器);DispatcherServlet 调用 HandlerAdapter(处理器适配器),适配 Handler 执行;HandlerAdapter 调用 Controller 中的目标方法,执行业务逻辑;Controller 方法返回 ModelAndView(含数据模型 Model 和视图名称 View);HandlerAdapter 将 ModelAndView 返回给 DispatcherServlet;DispatcherServlet 调用 ViewResolver(视图解析器),根据视图名称解析为实际视图(如 JSP、HTML);ViewResolver 返回 View 实例;DispatcherServlet 渲染 View(将 Model 数据填充到视图中),生成响应数据,返回给用户。
核心组件:DispatcherServlet(核心)、HandlerMapping、HandlerAdapter、ViewResolver、Controller。
发送请求经过网络到达项目的处理流程及原理
答:结合“网络分层+Spring MVC 流程”,完整流程如下:
客户端发起请求:浏览器/APP 构造 HTTP 请求(含 URL、请求头、请求体),通过网络分层模型传输(见问题27);
服务器接收请求:
请求到达服务器网卡,经过操作系统内核的 TCP/IP 协议栈解封装,最终交给 Web 容器(如 Tomcat);Tomcat 的 Connector(连接器)接收请求,将 HTTP 协议转换为 Tomcat 内部的 Request 对象;
Spring MVC 处理请求(见问题31):
Tomcat 将 Request 交给 DispatcherServlet,后续通过 HandlerMapping、HandlerAdapter 调用 Controller 处理业务;
业务逻辑处理:
Controller 调用 Service、DAO 层,与数据库(MySQL)、缓存(Redis)交互,完成数据处理;
响应返回:
Controller 返回 ModelAndView,经 ViewResolver 渲染后,Tomcat 将响应数据封装为 HTTP 响应,通过网络分层模型传输回客户端;
核心原理:分层解耦(网络层→Web 容器→框架层→业务层→数据层),每一层专注自身功能,通过标准接口交互。
Struts 相关问题
答:Struts 是“基于 MVC 设计模式的 Java Web 框架”,主要用于替代 Servlet 开发,核心知识点如下:
核心组件:
Action:处理用户请求(类似 Spring MVC 的 Controller),需继承 ActionSupport 类;ActionForm:封装请求参数(类似 JavaBean);ActionMapping:配置 URL 与 Action 的映射(在 struts-config.xml 中);View:视图层(JSP、HTML),通过 EL 表达式获取 Action 传递的数据;
工作流程:
用户发送请求→Struts 核心控制器 ActionServlet 拦截→根据 struts-config.xml 找到对应的 Action→ActionForm 封装请求参数→Action 执行业务逻辑→返回结果码(如 SUCCESS、ERROR)→ActionServlet 跳转至对应视图;
与 Spring MVC 的区别:
配置方式:Struts 依赖 XML 配置(struts-config.xml),Spring MVC 支持注解(@Controller、@RequestMapping),配置更简洁;灵活性:Spring MVC 集成 Spring 生态(如 IOC、AOP),支持更复杂的业务场景,Struts 灵活性较差;安全性:Struts 2 曾出现严重安全漏洞(如 OGNL 表达式注入),现在使用较少,Spring MVC 更主流;
若未使用过 Struts,可回答:“了解 Struts 是早期的 MVC 框架,核心功能是请求处理和视图跳转,但现在主流使用 Spring MVC,其配置更简洁、生态更完善,因此未深入使用 Struts,但理解其 MVC 设计思想与 Spring MVC 有共通之处。”
常用的 Web 容器(含 Tomcat 及之前的容器)
答:Web 容器(又称 Servlet 容器)是“运行 Java Web 应用的服务器软件”,核心功能是“解析 HTTP 请求、运行 Servlet/JSP、返回响应”,常用容器如下:
主流容器(当前常用):
Tomcat:Apache 开源,轻量级、易用性强,支持 Servlet/JSP,是 Java 后端开发的默认选择(开发环境+中小规模生产环境);Jetty:Eclipse 开源,轻量级、可嵌入式(如 Spring Boot 内置 Jetty 可选),适合高并发、快速迭代的场景;Undertow:JBoss 开源,高性能、低内存占用,支持 HTTP/2,Spring Boot 2.x 后默认支持;WebLogic:Oracle 商业容器,功能强大(支持分布式、集群),适合大型企业级应用(收费);WebSphere:IBM 商业容器,稳定性高,用于金融、政府等关键系统(收费);
早期容器(现在少用):
JBoss:早期开源容器,后来被 Red Hat 收购,集成了 EJB 容器,现在更多使用其衍生的 WildFly;Resin:Caucho 开源,支持 PHP/Java,早期以高性能著称,现在市场份额较小;
核心对比:Tomcat 平衡“性能、易用性、生态”,是大多数项目的首选;Jetty/Undertow 性能更优,适合高并发场景;商业容器功能更全,但成本高。
你做过的比较成功的事是什么
答:需选择与“能力提升、结果明确”相关的事,按“背景→目标→行动→结果→收获”逻辑回答,示例如下:
背景:大学期间参与“校园二手交易平台”开发项目,担任后端负责人,初期团队技术水平参差不齐,项目进度滞后;目标:3 个月内完成核心功能开发,保证系统稳定运行,支持 1000+ 学生同时使用;行动:
技术选型:确定 Spring Boot + MySQL + Redis 技术栈,编写详细开发文档,统一代码规范;任务拆分:将项目拆分为用户模块、商品模块、订单模块,分配给不同成员,制定周进度计划;问题解决:遇到高并发商品查询卡顿问题,引入 Redis 缓存热门商品数据,优化 SQL 索引,将响应时间从 500ms 降至 50ms;团队协作:每周组织技术分享会,帮助成员解决技术难题,确保整体进度;
结果:项目按时上线,累计注册用户 3000+,交易订单 500+,系统零故障运行,获得学校“优秀创业项目”;收获:提升了技术落地能力、团队管理能力和问题解决能力,理解了“需求→方案→落地”的完整流程。
在校期间参加过什么活动
答:选择与“团队协作、能力提升”相关的活动(如技术竞赛、志愿活动、学生工作),突出个人贡献和收获,示例如下:
技术竞赛:参加“蓝桥杯 Java 软件开发大赛”,团队 3 人合作完成“智能图书管理系统”,负责后端开发,通过优化数据库设计和接口逻辑,最终获得省级二等奖;
学生工作:担任学院技术部部长,组织“编程训练营”“技术讲座”等活动 10+ 场,覆盖学生 500+ 人次,提升了沟通协调和组织能力;
志愿活动:参与“乡村教育帮扶”活动,为乡村学生讲授编程基础课程,锻炼了表达能力和责任心;
核心:避免罗列活动,重点说明在活动中扮演的角色、做了什么、学到了什么,体现与岗位相关的能力(如团队协作、问题解决)。
方法重载的返回值必须一样吗
答:不一定。方法重载的核心判断依据是“方法名相同,参数列表不同”,返回值类型可相同可不同,具体规则如下:
方法重载(Overload)定义:发生在同一个类中(或父类与子类之间),方法名必须相同,参数列表(参数类型、个数、顺序)不同,与返回值类型、访问修饰符无关;示例:
// 合法重载(参数个数不同)
public int add(int a, int b) { return a + b; }
public int add(int a, int b, int c) { return a + b + c; }
// 合法重载(参数类型不同,返回值不同)
public String add(String a, String b) { return a + b; }
注意:仅返回值类型不同,参数列表相同,不构成重载(编译报错),如 和
public int add(int a, int b) 不合法。
public String add(int a, int b)
构造方法在继承中的特点
答:构造方法用于“初始化对象”,继承中遵循“先父后子”的初始化规则,核心特点如下:
子类构造方法默认调用父类无参构造方法:若子类构造方法未显式调用父类构造方法,编译器会自动添加 (无参),因此父类必须存在无参构造方法(否则编译报错);
super()
子类可显式调用父类构造方法:通过 调用父类有参构造方法,且必须放在子类构造方法的第一行;
super(参数)
构造方法不能被继承:子类不能继承父类的构造方法,只能通过 调用;
super()
示例:
class Father {
// 父类无参构造
public Father() { System.out.println("Father 无参构造"); }
// 父类有参构造
public Father(String name) { System.out.println("Father 有参构造:" + name); }
}
class Son extends Father {
public Son() {
super("张三"); // 显式调用父类有参构造,必须在第一行
System.out.println("Son 无参构造");
}
}
// 调用 new Son(),输出顺序:
// Father 有参构造:张三
// Son 无参构造
注意:若父类仅定义了有参构造,未定义无参构造,子类必须显式调用父类有参构造,否则编译报错。
抽象类里面只能有抽象方法吗
答:不是。抽象类(abstract class)中可以包含:抽象方法、非抽象方法(有具体实现)、静态方法、成员变量、构造方法,核心规则如下:
抽象方法:必须用 修饰,无方法体,子类必须重写(除非子类也是抽象类);非抽象方法:有方法体,子类可直接继承使用,也可重写;示例:
abstract
abstract class Animal {
// 成员变量
private String name;
// 构造方法(供子类调用)
public Animal(String name) { this.name = name; }
// 抽象方法(子类必须重写)
public abstract void eat();
// 非抽象方法(子类可直接使用)
public void sleep() { System.out.println(name + " 在睡觉"); }
// 静态方法
public static void show() { System.out.println("这是动物类"); }
}
class Dog extends Animal {
public Dog(String name) { super(name); }
// 重写抽象方法
@Override
public void eat() { System.out.println(name + " 吃骨头"); }
}
关键区别:接口(Java 8 前)只能有抽象方法,而抽象类可混合多种成员,核心作用是“代码复用+约束子类行为”。
接口和抽象类的共同点和区别
答:
共同点:
都不能被实例化(抽象类需子类继承,接口需类实现);都可以包含抽象方法(定义未实现的行为);都可用于多态(子类/实现类对象可赋值给父类/接口引用);Java 8+ 后,都可包含默认方法(抽象类用 修饰,接口默认
default)和静态方法;
public default
区别(同问题30,补充细节):
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 继承/实现限制 | 单继承(子类只能继承一个抽象类) | 多实现(类可实现多个接口) |
| 成员变量访问控制 | 可自定义(private/protected/public) | 仅 public static final(默认,必须初始化) |
| 构造方法 | 有(用于子类初始化) | 无 |
| 方法访问控制 | 可自定义(private/protected/public) | 抽象方法默认 public,default 方法默认 public |
| 设计理念 | 强调“is-a”关系(如 Dog is a Animal) | 强调“has-a”关系(如 Dog has a Runnable 行为) |
| 代码复用 | 可提供方法实现,便于子类复用 | 仅定义行为,无实现,复用性弱 |
C# 和 Java 的区别与共同点
答:
共同点:
都是面向对象语言(封装、继承、多态);都运行在虚拟机上(Java 运行在 JVM,C# 运行在 .NET CLR);语法相似(变量声明、循环结构、异常处理等);都支持垃圾回收(自动内存管理);都支持多线程、泛型、Lambda 表达式;
核心区别:
| 特性 | Java | C# |
|---|---|---|
| 跨平台性 | 强(JVM 跨平台,支持 Windows/Linux/macOS) | 弱(早期仅支持 Windows,.NET Core 后支持跨平台,但生态不如 Java) |
| 虚拟机/运行时 | JVM(独立于语言) | .NET CLR(主要为 C# 设计) |
| 语法细节 | 无指针(安全,但灵活性低) | 支持指针(需显式声明 unsafe 代码块) |
| 继承限制 | 类单继承,接口多实现 | 类单继承,接口多实现,支持委托(Delegate)、事件(Event) |
| 异常处理 | 必须捕获 checked 异常(如 IOException) | 无 checked 异常,所有异常均为 unchecked |
| 平台生态 | 后端开发(微服务、大数据)生态完善 | 桌面应用(Windows)、游戏开发(Unity)生态强 |
| 开发工具 | IntelliJ IDEA、Eclipse | Visual Studio(功能强大,集成度高) |
总结:Java 更适合跨平台后端开发,C# 更适合 Windows 应用和游戏开发,核心差异源于设计目标和生态侧重。
大学期间学的比较好的课程及相关问题
答:选择与 Java 后端相关的课程(如数据结构、计算机网络、数据库原理、操作系统),结合课程知识点和项目实践回答,示例如下:
课程:《数据结构与算法》学习成果:
掌握核心数据结构(数组、链表、栈、队列、哈希表、树、图)的原理和应用场景;熟练掌握常用算法(排序、查找、动态规划),并在 LeetCode 完成 200+ 题目;项目应用:在“校园二手交易平台”中,用哈希表优化商品查询(时间复杂度从 O(n) 降至 O(1)),用动态规划解决优惠券组合最优问题;
相关问题应对:若面试官追问具体知识点(如“哈希表的冲突解决方式”),可回答:“哈希表冲突解决有开放定址法(线性探测、二次探测)和链地址法,Java 中的 HashMap 采用链地址法,JDK 8 后当链表长度超过 8 会转为红黑树,提升查询效率。”
最常用的数据结构是什么?在项目中哪里用到?具体说明及源码了解情况
答:以“哈希表(HashMap)”为例,结合项目实践说明:
最常用数据结构:哈希表(Java 中的 HashMap),核心优势是“查询、插入、删除效率高(O(1) 平均复杂度)”;项目应用:在“用户权限管理系统”中,用 HashMap 缓存用户角色信息:
场景:用户登录后,需频繁查询其角色对应的权限列表,若每次查询数据库会影响性能;实现:用 缓存,key 为角色名(如 ADMIN),value 为权限列表(如 [“user:query”, “user:add”]);效果:首次查询从数据库加载数据到 HashMap,后续查询直接从缓存获取,响应时间从 200ms 降至 10ms;
HashMap<String, List<String>> rolePermissionMap
源码了解:
底层结构:JDK 8 前为“数组+链表”,JDK 8 后为“数组+链表+红黑树”;哈希函数:,减少哈希冲突;扩容机制:默认容量 16,负载因子 0.75,容量不足时扩容为原来的 2 倍;线程安全:非线程安全,并发场景下需使用 ConcurrentHashMap(分段锁/JDK 8 后 CAS 实现)。
key.hashCode() ^ (key.hashCode() >>> 16)
项目中遇到过什么问题?如何解决?调试时多个线程影响代码,如何确定具体是哪个线程的问题
答:
项目问题及解决:
问题:在“订单支付系统”中,高并发场景下出现订单重复支付问题;
原因:多个线程同时读取订单状态为“待支付”,均通过校验并执行支付逻辑;
解决:1. 数据库层面:给订单表添加乐观锁(version 字段),更新订单状态时校验版本号;2. 应用层面:用 Redis 分布式锁,以订单号为 key,确保同一订单同一时间只有一个线程执行支付逻辑;多线程问题定位(确定具体线程):
日志打印:在关键代码处打印线程 ID()和线程名(
Thread.currentThread().getId()),通过日志分析哪个线程执行异常;调试工具:
Thread.currentThread().getName()
IDEA 调试:开启多线程调试模式,断点处设置“线程过滤”,观察每个线程的执行流程;JDK 工具:用 获取进程 ID,用
jps 打印线程栈信息,查找阻塞/死锁的线程(如
jstack 进程ID 状态的线程);
BLOCKED
线程本地变量:用 存储线程上下文信息(如请求 ID),异常时打印请求 ID,关联到具体线程;
ThreadLocal
示例:通过 发现线程 ID 为 123 的线程因获取不到 Redis 锁而阻塞,进一步排查该线程的请求参数,发现是重复提交导致。
jstack
TCP 和 UDP 的区别
答:TCP(传输控制协议)和 UDP(用户数据报协议)是传输层核心协议,核心区别在于“可靠性”和“实时性”:
| 特性 | TCP | UDP |
|---|---|---|
| 可靠性 | 可靠(三次握手建立连接,四次挥手断开连接,重传丢失数据包,顺序保证) | 不可靠(无连接,数据包可能丢失、乱序、重复) |
| 连接性 | 面向连接(通信前需建立连接) | 无连接(通信前无需建立连接) |
| 数据传输 | 字节流(无数据边界,需应用层处理) | 数据报(有数据边界,一次发送一个数据报) |
| 拥塞控制/流量控制 | 支持(滑动窗口、慢启动机制) | 不支持(发送方无限制发送,可能导致网络拥塞) |
| 开销 | 高(连接建立、确认、重传等机制) | 低(无额外机制,头部仅 8 字节) |
| 适用场景 | 要求可靠传输的场景(HTTP、FTP、TCP 聊天) | 要求实时性的场景(视频通话、语音通话、DNS 查询) |
| 数据大小限制 | 无(字节流,可传输大量数据) | 有(单个数据报最大 65535 字节) |
HTTPS 和 HTTP 的区别及两者的工作流程
答:
核心区别:
| 特性 | HTTP | HTTPS |
|---|---|---|
| 安全性 | 明文传输,无加密,易被窃听、篡改 | 加密传输(SSL/TLS 协议),安全可靠 |
| 端口 | 80 | 443 |
| 连接建立 | 直接建立连接,无额外步骤 | 需先进行 SSL/TLS 握手,再传输 HTTP 数据 |
| 开销 | 低(无加密解密过程) | 高(加密解密、证书验证消耗资源) |
| 证书 | 无需证书 | 需 CA 颁发的数字证书(验证服务器身份) |
| 适用场景 | 非敏感数据传输(如静态网页) | 敏感数据传输(如登录、支付、电商) |
工作流程:
HTTP 工作流程:
客户端发送 HTTP 请求(如 GET /index.html)→ 服务器接收请求,返回 HTTP 响应(含页面数据)→ 连接关闭;
HTTPS 工作流程(SSL/TLS 握手+HTTP 传输):
① 客户端请求:客户端向服务器发送 HTTPS 请求,携带支持的 SSL/TLS 版本、加密套件;② 服务器响应:服务器返回数字证书(含公钥)、选择的加密套件;③ 客户端验证:客户端验证证书有效性(是否由可信 CA 颁发、是否过期),若有效则生成随机密钥(会话密钥),用服务器公钥加密后发送给服务器;④ 服务器解密:服务器用私钥解密,获取会话密钥;⑤ 加密传输:双方用会话密钥加密后续的 HTTP 数据(对称加密,效率高),完成请求和响应;核心:SSL/TLS 握手用于协商加密密钥和验证身份,后续数据传输用对称加密保证效率,公钥加密用于安全传递会话密钥。
有哪些加密算法
答:加密算法按“加密解密是否使用同一密钥”分为对称加密、非对称加密,另有哈希算法(不可逆),核心分类及示例如下:
对称加密算法(加密解密用同一密钥,效率高):
特点:加密速度快,适合大量数据加密,但密钥传输需安全渠道;常用算法:AES(高级加密标准,128/256 位密钥,目前最常用)、DES(56 位密钥,安全性低,已淘汰)、3DES(DES 改进版,安全性一般)、RC4(流式加密,易被破解);应用场景:HTTPS 数据传输、文件加密;
非对称加密算法(加密用公钥,解密用私钥,安全性高):
特点:密钥成对(公钥可公开,私钥需保密),无需安全传输密钥,但加密速度慢;常用算法:RSA(支持密钥交换、数字签名,应用最广)、ECC(椭圆曲线加密,密钥短、效率高,移动设备常用)、DSA(仅用于数字签名);应用场景:HTTPS 证书验证、密钥交换(如 HTTPS 握手时传递会话密钥);
哈希算法(不可逆,将任意长度数据转为固定长度哈希值):
特点:无法从哈希值反推原数据,相同数据哈希值相同,不同数据哈希值大概率不同(抗碰撞);常用算法:MD5(128 位,安全性低,易碰撞,仅用于校验)、SHA-1(160 位,已被破解)、SHA-256(256 位,安全性高,常用)、SHA-512(512 位,安全性更高,开销大);应用场景:密码存储(存储哈希值而非明文)、文件完整性校验(如下载文件校验哈希值)。
有哪些缓存策略
答:缓存策略用于“管理缓存数据的存储、失效、淘汰”,核心分为“缓存更新策略”和“缓存淘汰策略”:
缓存更新策略(保证缓存与数据库一致性):
Cache-Aside(旁路缓存):最常用,应用程序直接操作缓存和数据库,读时先查缓存,缓存 miss 查数据库并更新缓存;写时先更数据库,再删缓存(避免缓存脏数据);Write-Through(写穿):写操作先更新缓存,缓存再同步更新数据库,读操作直接查缓存,优点是缓存始终最新,缺点是写性能低;Write-Back(写回):写操作先更新缓存,缓存异步更新数据库(批量同步),优点是写性能高,缺点是可能丢失数据(缓存宕机);
缓存淘汰策略(缓存满时删除旧数据):
FIFO(先进先出):按数据进入缓存的顺序淘汰,简单但可能淘汰常用数据;LRU(最近最少使用):淘汰最近最少访问的数据,符合“局部性原理”,应用最广(如 Redis 默认支持);LFU(最不经常使用):淘汰访问次数最少的数据,适合长期使用频率稳定的场景;TTL(时间过期):给缓存数据设置过期时间,到期自动淘汰(如 Redis 的 EXPIRE 命令);
应用场景:大部分后端项目用“Cache-Aside + LRU + TTL”组合,兼顾一致性、性能和内存利用率。
写过哪些博客?主要学习方式是什么
答:按“真实情况+体现学习能力”回答,示例如下:
博客情况:在 CSDN/掘金 开设技术博客,累计发布 30+ 文章,主要围绕 Java 后端核心知识点,如《HashMap 源码解析》《Spring Boot 集成 Redis 实战》《MySQL 索引优化指南》,部分文章阅读量超 10000,收到不少同行的交流和认可;主要学习方式:
系统性学习:通过官方文档(如 Spring 官网)、技术书籍(《Java 并发编程实战》《MySQL 技术内幕》)构建知识体系;实战练习:在 LeetCode 刷算法题(200+ 题),参与开源项目(如给 Spring Boot 生态的小工具贡献代码),将理论转化为实践;问题驱动:遇到问题先独立排查(查日志、看源码),无法解决时参考 Stack Overflow、掘金文章,解决后总结成博客,加深记忆;交流学习:加入技术社群(如 Java 后端交流群),参与技术分享,与同行讨论难点问题,拓宽视野。
epoll 和 select 的区别,如何进行压测
答:
epoll 和 select 的区别(均为 I/O 多路复用技术,用于高效处理大量并发连接):
| 特性 | select | epoll |
|---|---|---|
| 文件描述符(fd)限制 | 有(默认 1024,需修改内核参数) | 无(仅受系统内存限制) |
| 效率 | 低(每次调用需遍历所有 fd,O(n)) | 高(基于事件驱动,仅处理就绪 fd,O(1)) |
| 数据拷贝 | 每次调用需将 fd 集合从用户态拷贝到内核态 | 仅初始化时拷贝,后续通过共享内存访问 |
| 触发模式 | 仅水平触发(LT):就绪 fd 未处理会反复通知 | 支持水平触发(LT)和边缘触发(ET):仅在状态变化时通知,效率更高 |
| 适用场景 | 少量并发连接(如小型服务) | 高并发连接(如 Nginx、Redis 等中间件) |
如何进行压测:
压测工具选择:
简单压测:ab(Apache Bench,适合 HTTP 接口,如 ,-n 总请求数,-c 并发数);复杂压测:JMeter(支持多协议、自定义场景,如模拟用户登录→查询→下单流程)、Locust(Python 编写,支持分布式压测,适合高并发场景);
ab -n 10000 -c 100 http://localhost:8080/test
压测指标关注:
吞吐量(QPS):每秒处理的请求数;响应时间(平均响应时间、P95/P99 响应时间):P95 表示 95% 的请求响应时间不超过该值;错误率:请求失败的比例(如超时、500 错误);服务器资源:CPU 使用率、内存占用、网络带宽;
压测步骤:
环境准备:压测机与服务器分离(避免资源竞争),确保服务器配置与生产环境一致;场景设计:模拟真实用户行为(如并发数从 100 逐步增加到 1000);结果分析:若 QPS 低、响应时间长,排查是否存在瓶颈(如数据库慢查询、缓存未命中、线程池参数不合理)。
如何判断一个链表是否有环
答:核心有两种方法,重点掌握“快慢指针法”:
方法 1:哈希表法(空间复杂度 O(n))
思路:遍历链表,用哈希表(如 Java 的 HashSet)存储已访问过的节点,若当前节点已在哈希表中,说明有环;若遍历到 null,说明无环;代码示例:
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while (head != null) {
if (set.contains(head)) {
return true;
}
set.add(head);
head = head.next;
}
return false;
}
方法 2:快慢指针法( Floyd 龟兔算法,空间复杂度 O(1),推荐)
思路:定义两个指针,快指针(fast)每次走 2 步,慢指针(slow)每次走 1 步;若链表有环,快慢指针终将相遇;若快指针走到 null,说明无环;代码示例:
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;
}
延伸:若需找到环的入口节点,可在快慢指针相遇后,将慢指针重置为头节点,然后快慢指针均每次走 1 步,再次相遇的节点即为环入口。
三次握手与四次挥手相关知识
答:三次握手用于“建立 TCP 连接”,四次挥手用于“断开 TCP 连接”,核心是“保证连接可靠建立和资源正确释放”:
三次握手(建立连接):
目的:确保双方收发能力正常,协商初始序列号(ISN);过程:
客户端 → 服务器(SYN=1,seq=x):客户端发送同步报文,请求建立连接,告知服务器自己的初始序列号 x;服务器 → 客户端(SYN=1,ACK=1,seq=y,ack=x+1):服务器响应同步+确认报文,告知客户端自己的初始序列号 y,并确认收到客户端的 x(ack=x+1);客户端 → 服务器(ACK=1,seq=x+1,ack=y+1):客户端发送确认报文,确认收到服务器的 y(ack=y+1),连接建立;
关键:第三次握手不可省略,否则服务器无法确认客户端已收到自己的响应,可能导致服务器资源浪费。
四次挥手(断开连接):
目的:确保双方数据均已传输完成,释放连接资源;过程(假设客户端先主动关闭):
客户端 → 服务器(FIN=1,seq=x):客户端发送结束报文,告知服务器不再发送数据;服务器 → 客户端(ACK=1,seq=y,ack=x+1):服务器确认收到结束报文,但可能还有数据未发送,进入 CLOSE-WAIT 状态;服务器 → 客户端(FIN=1,ACK=1,seq=z,ack=x+1):服务器数据发送完成,发送结束报文,告知客户端可关闭连接;客户端 → 服务器(ACK=1,seq=x+1,ack=z+1):客户端确认收到结束报文,进入 TIME-WAIT 状态(等待 2MSL,确保服务器收到确认),然后关闭连接;
关键:第四次挥手后客户端需等待 2MSL,避免服务器未收到确认而重发 FIN 报文。
线程和进程的区别
答:进程是“资源分配的最小单位”,线程是“CPU 调度的最小单位”,核心区别如下:
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 独立资源(内存空间、文件描述符、PID) | 共享进程资源(同一进程的线程共享内存、文件描述符) |
| 调度单位 | 操作系统调度进程,切换开销大 | 操作系统调度线程,切换开销小(无需切换资源) |
| 独立性 | 高(进程间地址空间独立,互不影响) | 低(线程间共享资源,一个线程崩溃可能导致整个进程崩溃) |
| 通信方式 | 复杂(管道、消息队列、共享内存、Socket) | 简单(共享内存、线程间变量、等待/通知机制) |
| 创建销毁开销 | 大(需分配资源) | 小(仅需创建线程栈) |
| 数量限制 | 较少(受内存、CPU 核心限制) | 较多(同一进程可创建多个线程) |
示例:打开一个浏览器是一个进程,浏览器中的多个标签页是该进程的多个线程,共享浏览器的内存资源(如缓存),切换标签页时线程切换开销小,响应更快。
CPU 如何分配进程
答:CPU 通过“进程调度算法”分配时间片(CPU 执行时间),核心目标是“提高 CPU 利用率、缩短响应时间、保证公平性”,常用调度算法如下:
批处理系统调度算法(适合无交互的后台任务):
先来先服务(FCFS):按进程到达顺序分配 CPU,简单但短进程可能等待长进程;短作业优先(SJF):优先分配给运行时间最短的进程,提高吞吐量,缺点是可能导致长进程饥饿;
分时系统调度算法(适合交互任务,如桌面应用):
时间片轮转(RR):给每个进程分配固定时间片(如 10ms),轮流执行,切换频繁,响应时间快;优先级调度:给进程分配优先级,高优先级进程优先执行,缺点是低优先级进程可能饥饿;
实时系统调度算法(适合实时任务,如工业控制):
抢占式优先级调度:高优先级进程可抢占低优先级进程的 CPU;截止时间优先(EDF):优先执行截止时间最早的进程;
调度过程:操作系统维护就绪队列,进程调度器从就绪队列中选择进程,分配 CPU 时间片,进程执行完时间片或因 I/O 阻塞时,回到就绪队列,等待下一次调度。
跳表相关知识
答:跳表(Skip List)是“有序链表的优化数据结构”,核心是“通过多级索引提升查询效率”,适用于高并发场景(如 Redis 的 ZSet 底层实现):
结构特点:
底层是有序链表(最底层包含所有元素);上层是索引层,每个索引节点
跳表相关知识
答:跳表(Skip List)是有序链表的优化数据结构,核心通过“多级索引”降低查询复杂度,适用于高并发场景(如 Redis 的 ZSet 底层实现),核心知识点如下:
结构特点:
底层是完整的有序链表(最底层包含所有元素);上层是索引层,每个索引节点指向下方层级的对应节点(如第 2 层索引节点指向第 1 层的多个节点);索引层级随机生成(插入元素时,通过随机算法决定索引层数,通常最高层为 log₂n,保证查询效率);
核心操作(查询、插入、删除):
查询:从最高层索引开始,依次向下遍历,遇到比目标值大的节点则下沉一层,最终在底层链表找到目标(时间复杂度 O(log n));插入:先找到插入位置,再生成随机索引层数,创建索引节点并插入对应层级,维持链表有序;删除:找到目标节点及其所有索引节点,依次删除,确保各层级索引一致性;
优缺点:
优点:查询、插入、删除效率均为 O(log n),比普通链表(O(n))高效,且实现比平衡二叉树简单;缺点:需额外存储索引,空间复杂度 O(n)(索引占用约 50% 额外空间);
应用场景:Redis ZSet(有序集合)、LevelDB 等,用于需要高效有序查询的场景。
是否用过 k8s、opencv
答:(根据实际情况回答,若未使用过,可按以下逻辑说明)
对于 k8s:了解其核心作用是“容器编排”,用于管理 Docker 容器的部署、扩缩容、负载均衡、故障恢复,核心组件包括 Master(控制节点)、Node(工作节点)、Pod(最小部署单元)、Service(服务暴露);虽未实际搭建过,但通过官方文档和技术课程学习了基础架构,能理解其在分布式系统中的部署价值(如微服务集群化管理)。对于 opencv:了解其是“开源计算机视觉库”,核心用途是图像识别、目标检测、图像处理(如人脸检测、图像滤波),支持 Java、Python 等语言调用;虽未实际开发过相关项目,但了解其在 AI 视觉场景中的应用(如智能监控、自动驾驶),具备快速上手使用的学习能力。补充:若后续工作涉及相关技术,会通过官方文档、实战教程快速掌握,目前更聚焦于 Java 后端核心技术,但始终保持对云原生、AI 相关工具的学习兴趣。
为什么选择后端开发
答:核心围绕“兴趣匹配、能力契合、行业价值”展开,示例如下:
兴趣驱动:喜欢逻辑思维和问题解决,后端开发需设计系统架构、处理数据流转、解决并发/一致性问题,这种“从抽象设计到落地实现”的过程让我有强烈的成就感;
能力契合:大学期间系统学习了 Java、数据结构、数据库、计算机网络等核心知识,通过项目实践掌握了 Spring 生态、微服务架构,能力与后端岗位要求高度匹配;
行业价值:后端是系统的核心骨架,负责数据存储、业务逻辑处理、服务协作,直接决定系统的稳定性、性能和可扩展性,是互联网产品的基石,具有长期发展空间;
个人追求:希望通过技术解决实际业务问题(如高并发订单处理、数据安全存储),后端开发能让我深入业务核心,实现技术落地价值。
学习 Java 的流程计划
答:采用“循序渐进、理论+实践”的学习路径,分 4 个阶段:
阶段 1:基础夯实(1-2 个月)
核心内容:Java 语法(变量、循环、异常)、面向对象(封装、继承、多态)、集合框架(ArrayList、HashMap 等)、IO 流、反射;实践:完成小型工具开发(如文件批量处理工具),刷 100+ 基础语法题;
阶段 2:进阶提升(2-3 个月)
核心内容:JVM(内存模型、GC 机制)、多线程(线程池、并发工具)、MySQL(索引、事务、SQL 优化)、Spring 生态(Spring、Spring Boot、MyBatis);实践:开发单体应用(如个人博客系统、电商后台管理系统),重点关注性能优化和数据一致性;
阶段 3:技术拓展(3-4 个月)
核心内容:微服务架构(Spring Cloud、Dubbo)、中间件(Redis、RabbitMQ、Elasticsearch)、分布式系统(分布式锁、分布式事务、服务熔断降级);实践:开发微服务项目(如分布式电商订单系统),掌握服务注册发现、配置中心、链路追踪等核心能力;
阶段 4:深度沉淀(持续)
核心内容:源码阅读(Spring、JDK 集合)、高并发架构设计(分库分表、读写分离)、云原生技术(Docker、k8s 基础);实践:参与开源项目、攻克复杂技术难点(如高并发场景下的流量削峰),形成技术博客和知识体系。
对 DDD 架构的理解及劣势
答:DDD(Domain-Driven Design,领域驱动设计)是以业务领域为核心的软件设计方法论,核心是让技术设计贴合业务逻辑,避免“技术与业务脱节”。
核心理解:
核心思想:将系统按业务领域拆分(如电商领域拆分为订单域、用户域、商品域),每个领域为“限界上下文”(独立的业务边界和技术实现);核心组件:实体(有唯一标识的业务对象,如订单)、值对象(无唯一标识,如地址)、聚合根(领域的核心入口,如订单是订单项的聚合根)、领域服务(处理跨实体的业务逻辑);优势:业务逻辑集中在领域层,系统可维护性、可扩展性强,适合复杂业务场景(如金融、电商);
劣势:
学习成本高:需深入理解业务领域,掌握大量 DDD 概念(限界上下文、聚合根等),团队需统一认知;小项目性价比低:简单业务(如静态网站、小型工具)无需复杂拆分,DDD 会增加设计和开发成本;依赖团队协作:需业务人员与开发人员紧密配合,明确领域模型,否则易设计出“伪 DDD”架构;技术复杂度高:限界上下文间的通信(如领域事件、RPC)需额外设计,增加系统复杂度。
多线程如何使用
答:Java 中多线程的核心实现方式有 4 种,结合使用场景说明:
方式 1:继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行:" + Thread.currentThread().getName());
}
}
// 使用:new MyThread().start();
场景:简单线程需求,缺点是无法继承其他类(Java 单继承);方式 2:实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行:" + Thread.currentThread().getName());
}
}
// 使用:new Thread(new MyRunnable()).start();
场景:需继承其他类的场景,缺点是无法返回执行结果;方式 3:实现 Callable + Future(支持返回结果和异常捕获)
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1 + 1; // 返回执行结果
}
}
// 使用:
FutureTask<Integer> future = new FutureTask<>(new MyCallable());
new Thread(future).start();
Integer result = future.get(); // 获取结果(阻塞直到完成)
场景:需要线程返回结果的场景(如异步计算);方式 4:使用线程池(推荐,避免线程创建销毁开销)
ExecutorService executor = Executors.newFixedThreadPool(5); // 固定 5 个线程
executor.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程池执行任务");
}
});
executor.shutdown(); // 关闭线程池
场景:高并发场景(如接口请求处理),核心优势是线程复用、控制并发数、便于管理;核心注意事项:多线程需保证线程安全(如使用 synchronized、Lock),避免共享资源竞争导致的数据异常。
大数据量情况下如何迁移数据和添加索引
答:核心原则是“减少对业务的影响(如锁表、性能下降),保证数据一致性”。
一、大数据量数据迁移(如千万级数据从旧库迁移到新库):
分批次迁移:
按主键 ID 分片(如每次迁移 1 万条,where id between 1 and 10000),避免一次性读取大量数据导致内存溢出;非峰值时段迁移(如凌晨),减少对业务查询的影响;
读写分离策略:
先将新写数据同步到新旧两个库(双写),再迁移历史数据;历史数据迁移完成后,切换读流量到新库,最后停止旧库写入;
工具辅助:使用 DataX、Flink 等工具,支持高并发、断点续传,避免手动编写迁移脚本的低效和风险;数据校验:迁移后对比新旧库的总条数、关键字段哈希值,确保数据一致性。
二、大数据量添加索引(如千万级表添加联合索引):
避开业务高峰期:选择凌晨等低流量时段,避免索引创建过程中锁表(MyISAM 会锁全表,InnoDB 5.6+ 支持在线 DDL,锁表时间短);先建备库索引:
在从库(备库)添加索引,同步完成后切换主从(将备库升为主库);优点是完全不影响主库业务;
使用在线 DDL 工具:MySQL 5.6+ 支持 ALGORITHM=INPLACE, LOCK=NONE(无锁添加索引),避免锁表;避免大事务:添加索引期间禁止长事务,否则会导致锁等待,延长添加时间。
数据库表字段设计(结合具体功能逻辑说明)
答:以“电商订单支付功能”为例,说明字段设计需“贴合业务逻辑、保证数据完整性、优化查询效率”:
业务逻辑:订单需记录用户信息、商品信息、支付状态、金额、时间等,支持“查询用户订单”“按状态筛选订单”“统计支付金额”等场景;表字段设计:
CREATE TABLE `order_pay` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单支付ID(主键)',
`order_no` varchar(32) NOT NULL COMMENT '订单号(唯一索引,关联订单主表)',
`user_id` bigint(20) NOT NULL COMMENT '用户ID(索引,用于查询用户订单)',
`pay_amount` decimal(10,2) NOT NULL COMMENT '支付金额(非空,避免NULL处理)',
`pay_type` tinyint(1) NOT NULL COMMENT '支付方式:1-微信,2-支付宝,3-银行卡(字典映射,节省空间)',
`pay_status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '支付状态:0-待支付,1-已支付,2-支付失败(默认值,简化逻辑)',
`transaction_no` varchar(64) DEFAULT NULL COMMENT '第三方支付流水号(如微信支付单号,可NULL)',
`pay_time` datetime DEFAULT NULL COMMENT '支付时间(支付成功后更新)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(索引,用于按时间筛选)',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_order_no` (`order_no`), -- 唯一约束,避免重复支付
KEY `idx_user_id` (`user_id`), -- 支持用户订单查询
KEY `idx_create_time` (`create_time`), -- 支持按时间筛选
KEY `idx_pay_status` (`pay_status`) -- 支持按支付状态筛选
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单支付表';
设计逻辑说明:
核心字段:order_no(关联订单主表)、user_id(用户关联)、pay_amount(金额)、pay_status(状态),覆盖核心业务场景;数据类型:金额用 decimal(避免浮点精度误差),状态用 tinyint(节省空间),时间用 datetime(便于查询);约束:主键保证唯一,唯一索引避免重复支付,非空约束确保核心字段有值;索引:针对高频查询场景(用户订单、时间筛选、状态筛选)建立索引,提升查询效率;扩展字段:transaction_no(第三方流水号)、update_time(更新时间),满足后续对账、问题排查需求。
线程池的核心参数、拒绝策略、并发执行时的执行流程及种类
答:线程池是“管理线程的容器”,核心是线程复用,避免线程创建销毁开销。
核心参数(ThreadPoolExecutor 构造函数参数):
corePoolSize:核心线程数(长期存活的线程数,即使空闲也不销毁,除非 allowCoreThreadTimeOut=true);maximumPoolSize:最大线程数(线程池能容纳的最大线程数);keepAliveTime:非核心线程空闲存活时间(超过该时间则销毁);unit:keepAliveTime 的时间单位(如 TimeUnit.SECONDS);workQueue:阻塞队列(用于存储等待执行的任务,如 ArrayBlockingQueue);threadFactory:线程工厂(用于创建线程,可自定义线程名、优先级);handler:拒绝策略(任务队列满且线程数达 maximumPoolSize 时,处理新任务的策略);
拒绝策略(4 种默认策略):
AbortPolicy(默认):直接抛出 RejectedExecutionException 异常,拒绝新任务;CallerRunsPolicy:让提交任务的线程自己执行(如主线程提交,主线程执行),缓解任务压力;DiscardPolicy:直接丢弃新任务,不抛出异常;DiscardOldestPolicy:丢弃队列中最旧的任务(队列头部任务),再尝试提交新任务;
并发执行流程:新任务提交→核心线程是否空闲?是→核心线程执行;否→进入步骤 2;阻塞队列是否已满?否→任务入队等待;是→进入步骤 3;线程数是否达 maximumPoolSize?否→创建非核心线程执行;是→执行拒绝策略;
常用线程池种类(Executors 工具类创建):
FixedThreadPool:核心线程数=最大线程数,无界队列(LinkedBlockingQueue),适合任务量稳定的场景;CachedThreadPool:核心线程数=0,最大线程数=Integer.MAX_VALUE,同步队列(SynchronousQueue),适合短期、高频任务(线程空闲 60 秒销毁);ScheduledThreadPool:核心线程数固定,支持定时/周期性任务(如延迟 10 秒执行);SingleThreadExecutor:核心线程数=最大线程数=1,无界队列,适合单线程串行执行任务(避免并发问题)。
线程和底层操作系统的关系
答:Java 线程依赖操作系统线程实现,遵循“1:1 映射模型”(一个 Java 线程对应一个操作系统线程),核心关系如下:
线程创建:Java 中通过 new Thread() 创建线程,最终调用操作系统的底层 API(Linux 中是 pthread_create,Windows 中是 CreateThread),由操作系统分配线程资源(如线程栈、寄存器);
线程调度:Java 线程的调度由操作系统完成(JVM 不参与调度),操作系统通过“时间片轮转”或“优先级调度”分配 CPU 时间片,Java 线程的状态(RUNNABLE、BLOCKED 等)对应操作系统线程的状态(就绪、运行、阻塞);
资源共享:Java 线程共享所属进程的资源(如堆内存、文件描述符),这是操作系统层面的进程资源共享机制决定的;
线程销毁:Java 线程执行完 run() 方法或调用 stop()(不推荐)后,操作系统会回收线程资源(线程栈、寄存器等);
核心结论:Java 线程是操作系统线程的“包装器”,JVM 负责与操作系统交互,而线程的实际执行和调度由操作系统控制。
并发线程在多核 CPU 时是否每个核心都会用到?单核 CPU 情况下如何执行
答:
多核 CPU 场景:会用到多个核心;
操作系统的线程调度器会将不同的 Java 线程分配到不同的 CPU 核心上,线程可以并行执行(多个线程同时在不同核心上运行),充分利用多核资源,提升系统吞吐量;示例:4 核 CPU 运行 4 个线程,每个核心负责一个线程,并行执行,效率是单核的 4 倍左右(忽略调度开销)。
单核 CPU 场景:不会用到多个核心,线程并发执行(宏观并行,微观串行);
操作系统通过“时间片轮转调度”,给每个线程分配固定的 CPU 时间片(如 10ms),线程轮流占用 CPU 执行;从宏观上看,多个线程同时在执行,但微观上每个时刻只有一个线程在 CPU 上运行,其他线程处于就绪或阻塞状态;示例:单核 CPU 运行 4 个线程,每个线程执行 10ms 后切换到下一个,循环往复,最终完成所有任务。
等待队列的种类
答:Java 中的等待队列主要用于“线程间协作”(如等待通知机制),核心分为 3 类:
synchronized 相关等待队列(基于 Object 监视器):
EntryList(阻塞队列):线程尝试获取 synchronized 锁失败时,进入 EntryList 阻塞,等待锁释放;WaitSet(等待队列):线程调用 wait() 方法后,释放锁并进入 WaitSet 等待,需其他线程调用 notify()/notifyAll() 唤醒,唤醒后重新进入 EntryList 竞争锁;
线程池阻塞队列(用于存储线程池等待执行的任务):
ArrayBlockingQueue:基于数组的有界阻塞队列,FIFO 顺序,需指定容量;LinkedBlockingQueue:基于链表的无界/有界阻塞队列,FIFO 顺序,默认无界(容量 Integer.MAX_VALUE);SynchronousQueue:同步队列,无容量,提交任务必须有线程立即接收(如 CachedThreadPool 使用);PriorityBlockingQueue:优先级阻塞队列,按任务优先级排序,无界;DelayQueue:延迟队列,任务需实现 Delayed 接口,延迟时间到后才能被取出;
Lock 相关 Condition 队列(基于 AQS 同步器):
每个 Condition 对象对应一个独立的等待队列,线程调用 Condition.await() 时进入队列,调用 Condition.signal()/signalAll() 唤醒;支持多条件等待(如一个 Lock 可创建多个 Condition,实现不同场景的线程协作)。
线程池任务执行完后如何后续处理
答:线程池任务执行完后的处理逻辑围绕“线程管理、资源释放、队列清理”展开:
线程处理:
核心线程:默认情况下,核心线程会保持存活状态,等待新任务(即使空闲);若设置 allowCoreThreadTimeOut(true),核心线程空闲时间超过 keepAliveTime 也会被销毁;非核心线程:任务执行完后,若空闲时间超过 keepAliveTime,会被线程池销毁,释放线程资源;
队列处理:
有界队列:任务执行完后,队列会被清空(无等待任务);无界队列:若任务持续提交,队列会一直积累任务,需手动控制任务提交速率,或通过拒绝策略限制;
资源释放:
主动关闭:调用线程池的 shutdown() 方法(平缓关闭,等待所有任务执行完后销毁线程)或 shutdownNow() 方法(立即关闭,中断正在执行的任务,清空队列);被动关闭:若线程池无引用且所有线程都已销毁,JVM 会自动回收线程池资源;
后续优化:
任务执行结果处理:通过 Future 或回调函数获取任务执行结果,处理异常(如任务执行抛出异常,需捕获并记录日志);线程池监控:通过 ThreadPoolExecutor 的 getActiveCount()(活跃线程数)、getCompletedTaskCount()(已完成任务数)等方法监控线程池状态,优化核心参数(如调整核心线程数、队列容量)。
最左前缀法则和索引下推相关知识
答:两者都是 MySQL 索引优化的核心知识点,用于提升查询效率。
最左前缀法则(针对联合索引):
定义:对于联合索引(如 idx_a_b_c (a, b, c)),MySQL 只会在查询条件满足“最左前缀”时使用该索引;生效场景:查询条件包含 a(左前缀第一位)、a+b(左前缀前两位)、a+b+c(完整左前缀),索引生效;失效场景:查询条件为 b、c、b+c,索引失效(未匹配最左前缀);示例:
-- 联合索引 idx_a_b_c
SELECT * FROM table WHERE a = 1; -- 生效
SELECT * FROM table WHERE a = 1 AND b = 2; -- 生效
SELECT * FROM table WHERE b = 2 AND c = 3; -- 失效
注意:查询条件中 a 的顺序不影响(如 WHERE b = 2 AND a = 1,MySQL 优化器会调整顺序),但必须包含最左前缀。
索引下推(Index Condition Pushdown,ICP,MySQL 5.6+ 特性):
定义:将查询的过滤条件(WHERE 子句)下推到存储引擎层,在索引遍历过程中直接过滤不符合条件的数据,无需将数据回表后再过滤;核心价值:减少回表次数(回表是从索引找到主键,再到聚簇索引查询完整数据),提升查询效率;示例:
-- 表有联合索引 idx_name_age (name, age),查询条件:name LIKE '张%' AND age > 20
-- 无 ICP:存储引擎返回所有 name LIKE '张%' 的索引记录,服务器层过滤 age > 20;
-- 有 ICP:存储引擎遍历索引时,同时判断 age > 20,只返回符合条件的记录,减少回表次数。
启用方式:默认启用,可通过 开启。
SET optimizer_switch = 'index_condition_pushdown=on'
Raft 算法及与其他算法的对比
答:Raft 是分布式系统一致性算法,核心目标是“在不可靠的网络环境中,让多个节点达成数据一致”(如分布式数据库、配置中心)。
核心理解:
核心思想:将一致性问题拆分为“领导者选举”“日志复制”“安全性”三个独立模块,比 Paxos 算法更易理解和实现;核心角色:Leader(领导者,处理所有客户端请求,复制日志到追随者)、Follower(追随者,被动接收 Leader 日志,参与选举)、Candidate(候选人,Leader 故障时发起选举);核心流程:
选举:集群启动或 Leader 故障时,Follower 转为 Candidate,发起投票,获得多数节点支持则成为新 Leader;复制:Leader 接收客户端请求,将日志条目复制到所有 Follower,待多数 Follower 确认后,Leader 提交日志并返回结果给客户端;安全性:确保已提交的日志条目不会被覆盖,Leader 始终包含所有已提交的日志。
与 Paxos 算法的对比:
| 特性 | Raft | Paxos |
|---|---|---|
| 复杂度 | 低(拆分模块,流程清晰) | 高(理论复杂,难以实现) |
| 可理解性 | 强(文档详细,示例丰富) | 弱(理论抽象,缺乏直观示例) |
| 核心流程 | 选举→复制→提交(线性流程) | 准备→接受→学习(两阶段提交) |
| Leader 角色 | 明确(必须有 Leader 才能处理请求) | 不明确(可无 Leader,直接提交) |
| 应用场景 | Etcd、Consul、Redis Cluster | Chubby、ZooKeeper(基于 Paxos 变种) |
总结:Raft 是 Paxos 的“工程化优化”,更适合实际系统实现,目前是分布式一致性算法的主流选择。
科研方向介绍
答:(根据实际科研经历回答,若无科研经历,可说明关注的科研方向)
示例(有科研经历):
科研方向是“分布式系统数据一致性优化”,核心围绕“高并发场景下的分布式事务处理”展开:
研究背景:分布式系统中,跨节点数据修改需保证一致性,但传统 2PC/3PC 协议性能低,无法满足高并发需求;研究内容:基于 Raft 算法设计轻量化分布式事务协议,通过“日志异步复制+本地事务提交”优化性能,减少跨节点通信开销;技术方案:使用 Java 实现原型系统,集成 Redis 缓存热点数据,通过 JMeter 压测验证性能;研究成果:优化后的协议吞吐量比 2PC 提升 30%,延迟降低 40%,相关论文被某核心期刊收录。
示例(无科研经历):
虽无正式科研项目经历,但长期关注“人工智能与后端开发的结合”方向,尤其是“大语言模型在接口自动化测试中的应用”:
学习内容:了解 LLMOps 相关技术,尝试使用 LangChain 框架开发接口测试工具,通过自然语言描述生成测试用例和 SQL 语句;研究规划:后续计划深入学习大模型微调、提示工程,探索如何将 AI 工具集成到后端开发流程中,提升开发效率(如自动生成接口文档、排查 SQL 错误)。
作为负责人如何安排项目推进
答:核心原则是“明确目标、拆分任务、把控进度、规避风险”,具体步骤如下:
需求拆解与目标对齐:
组织需求评审会,联合产品、开发、测试团队明确需求边界、核心功能和验收标准;将项目目标拆解为可量化的里程碑(如“3 周内完成核心接口开发,4 周内完成系统测试”);
任务分配与责任到人:
按功能模块拆分任务(如用户模块、订单模块),结合团队成员的技术特长和工作量合理分配;明确每个任务的负责人、截止时间、依赖资源(如接口依赖其他团队提供),通过 JIRA 等工具跟踪任务状态;
进度管理与沟通协作:
每日站会:15 分钟同步进度(完成情况、遇到的问题、今日计划),及时解决阻塞问题;周报同步:每周向团队和领导汇报项目进度、风险和下一步计划;跨团队沟通:若依赖其他团队资源(如第三方接口),指定专人对接,确保沟通高效;
质量把控与风险预警:
代码评审:核心模块代码必须经过交叉评审,避免逻辑错误和性能问题;测试同步:开发过程中同步编写单元测试、接口测试,避免后期集中出现大量 Bug;风险预案:识别潜在风险(如技术难点、资源不足),制定应对方案(如提前调研技术方案、申请额外资源);
复盘总结与优化:
项目结束后,组织复盘会,总结成功经验和问题(如“需求变更频繁导致延期”);形成文档沉淀(如开发手册、问题解决方案),为后续项目提供参考。
需求开发冲突如何解决
答:需求冲突常见于“产品需求与技术实现矛盾”“不同团队需求优先级冲突”,解决核心是“沟通理解、优先级排序、方案折中”:
冲突拆解与诉求明确:
首先厘清冲突本质:是技术无法实现?还是资源不足?或是需求优先级不清晰?组织相关方(产品、开发、业务)沟通,了解各方诉求(如产品追求功能完整性,开发追求技术可行性,业务追求上线时间);
优先级排序与取舍:
基于“业务价值(是否核心功能)、紧急程度(是否影响上线)、技术成本(实现难度和时间)”排序;示例:核心功能(如支付接口)优先实现,非核心功能(如统计报表)可延期;紧急需求(如修复线上 Bug)优先于新功能开发;
方案折中与共赢:
若技术无法实现产品需求,提供替代方案(如“无法实现实时统计,可改为 T+1 统计,降低技术复杂度”);若资源不足,协商分期实现(如“V1 版本实现核心功能,V2 版本迭代优化”);
上报决策与文档记录:
若各方无法达成一致(如业务方坚持高优先级,开发方无资源支持),上报领导或项目委员会决策;决策后形成书面文档(如需求变更单),明确最终方案、责任人、时间节点,避免后续争议。
Linux 如何查看文件?如何根据关键词搜索文件
答:
一、Linux 查看文件的常用命令:
cat:查看文件全部内容(适合小文件)
cat filename.txt # 直接查看
cat -n filename.txt # 显示行号
more/less:分页查看文件(适合大文件)
more filename.txt # 向下分页,按空格翻页,q 退出
less filename.txt # 双向分页,支持上下箭头滚动,q 退出
head/tail:查看文件开头/结尾内容
head -n 10 filename.txt # 查看前 10 行
tail -n 10 filename.txt # 查看后 10 行
tail -f filename.txt # 实时监控文件新增内容(如日志文件)
grep:查看文件中包含关键词的内容(结合搜索使用)
grep "keyword" filename.txt # 查找包含 keyword 的行
二、根据关键词搜索文件的常用命令:
grep:搜索文件内容中的关键词(最常用)
grep "keyword" filename.txt # 搜索单个文件
grep -r "keyword" /path # 递归搜索 /path 目录下所有文件
grep -n "keyword" filename.txt # 显示匹配行号
grep -i "keyword" filename.txt # 忽略大小写匹配
grep -v "keyword" filename.txt # 反向匹配(不包含 keyword 的行)
find:根据文件名/文件属性搜索文件路径
find /path -name "filename.txt" # 按文件名搜索
find /path -name "*.java" # 按后缀名搜索(所有 .java 文件)
find /path -size +10M # 搜索大于 10M 的文件
find /path -mtime -3 # 搜索 3 天内修改过的文件
locate:快速搜索文件(基于系统数据库,需先更新数据库:updatedb)
locate filename.txt # 快速查找文件路径,比 find 快
wait() 和 sleep() 的区别?sleep(0) 有什么用
答:
一、wait() 和 sleep() 的核心区别:
| 特性 | wait() | sleep() |
|---|---|---|
| 所属类 | java.lang.Object(所有对象都有) | java.lang.Thread(静态方法) |
| 释放锁 | 释放(必须在 synchronized 代码块中调用,释放对象锁) | 不释放(持有锁进入睡眠,其他线程无法获取锁) |
| 唤醒方式 | 其他线程调用 notify()/notifyAll() 唤醒,或超时自动唤醒 | 超时自动唤醒,或调用 interrupt() 中断 |
| 使用场景 | 线程间通信(如生产者消费者模式,等待资源就绪) | 线程暂停执行(如模拟延时,不涉及锁竞争) |
| 参数要求 | 可指定超时时间(毫秒),也可无参数(无限等待) | 必须指定超时时间(毫秒),或 sleep(0) |
二、sleep(0) 的作用:
触发操作系统的“重新调度”:让当前线程主动放弃 CPU 时间片,操作系统会重新计算所有就绪线程的优先级,将 CPU 分配给优先级更高的线程;适用场景:避免当前线程长时间占用 CPU,给其他优先级更高的线程执行机会(如在循环中调用 sleep(0),防止线程“饿死”其他线程);注意:sleep(0) 不会让线程睡眠,只是触发一次调度,线程很快会重新获得 CPU 时间片。
MySQL 常见隔离级别、默认级别、场景判断及底层引擎区别
答:MySQL 的隔离级别用于“解决并发事务带来的问题(脏读、不可重复读、幻读)”,核心基于 SQL 标准定义。
常见隔离级别(从低到高):
| 隔离级别 | 脏读(读取未提交数据) | 不可重复读(同一事务多次读同一数据结果不同) | 幻读(同一事务多次查询结果行数不同) |
|---|---|---|---|
| 读未提交(Read Uncommitted) | 允许 | 允许 | 允许 |
| 读已提交(Read Committed) | 禁止 | 允许 | 允许 |
| 可重复读(Repeatable Read) | 禁止 | 禁止 | 禁止(InnoDB 特殊处理) |
| 串行化(Serializable) | 禁止 | 禁止 | 禁止 |
默认级别:InnoDB 存储引擎默认“可重复读(Repeatable Read)”,MyISAM 不支持事务,无隔离级别;
场景判断示例:
场景 1:银行转账,A 转 B 100 元,A 扣款后事务未提交,B 读取余额已增加(脏读)→ 需至少设置“读已提交”级别;场景 2:电商下单,同一用户同一订单多次查询库存,因其他订单扣减库存导致结果不同(不可重复读)→ 需设置“可重复读”级别;场景 3:统计订单数,同一事务两次查询,因其他事务新增订单导致行数不同(幻读)→ 需设置“串行化”级别(或依赖 InnoDB 的 next-key 锁);
底层引擎区别:
InnoDB:支持事务和所有隔离级别,通过“锁机制”和“MVCC(多版本并发控制)”实现隔离级别(如读已提交通过 MVCC 避免脏读,可重复读通过 next-key 锁避免幻读);MyISAM:不支持事务,仅支持表级锁,无隔离级别概念,并发性能差,适合读多写少的非事务场景。
如何保证多线程开发的安全性
答:多线程安全的核心是保证“原子性、可见性、有序性”,常用方案如下:
锁机制(保证原子性和可见性):
synchronized:内置锁,自动释放,支持方法锁和代码块锁,适合简单场景;Lock 接口:手动锁(ReentrantLock、ReentrantReadWriteLock),支持公平锁、非公平锁、条件等待,适合复杂场景(如读写分离);示例(synchronized):
private int count = 0;
public synchronized void increment() {
count++; // 原子操作
}
volatile 关键字(保证可见性和有序性,不保证原子性):
用于修饰共享变量,避免线程缓存导致的“可见性问题”(一个线程修改后,其他线程立即可见);禁止指令重排序(如单例模式 DCL 中修饰 instance);示例:
private volatile boolean flag = false;
原子类(保证原子性):
java.util.concurrent.atomic 包下的类(如 AtomicInteger、AtomicReference),基于 CAS 操作实现原子性,无锁机制,效率高;示例:
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子自增
}
并发工具类(控制并发数和线程协作):
Semaphore:信号量,控制同时访问的线程数(如限制 10 个线程同时访问数据库);CountDownLatch:倒计时锁,等待多个线程完成后再执行(如主线程等待所有子线程执行完);CyclicBarrier:循环栅栏,多个线程到达屏障后再一起执行;
避免共享资源(根本方案):
线程本地存储(ThreadLocal):每个线程有独立的变量副本,避免共享(如存储用户会话信息);无状态设计:线程执行不依赖共享变量(如接口开发中,每个请求的处理逻辑独立)。
Java IO 流用过哪些?如果不关闭文件会有什么后果
答:
一、常用 Java IO 流:
IO 流按“数据类型”分为字节流和字符流,按“流向”分为输入流和输出流:
字节流(处理二进制数据,如文件、图片):
输入流:InputStream(抽象类)→ FileInputStream(文件输入流)、BufferedInputStream(缓冲输入流);输出流:OutputStream(抽象类)→ FileOutputStream(文件输出流)、BufferedOutputStream(缓冲输出流);
字符流(处理文本数据,如 TXT 文件):
输入流:Reader(抽象类)→ FileReader(文件输入流)、BufferedReader(缓冲输入流,支持 readLine() 读取整行);输出流:Writer(抽象类)→ FileWriter(文件输出流)、BufferedWriter(缓冲输出流,支持 newLine() 换行);
转换流(字节流与字符流互转):InputStreamReader、OutputStreamWriter;示例(使用 BufferedReader 读取文件):
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
二、不关闭文件流的后果:
文件句柄泄露:操作系统的文件句柄数量有限,不关闭流会导致文件句柄被占用,耗尽后无法打开新文件;数据丢失:缓冲流(如 BufferedWriter)会将数据存入缓冲区,未关闭流时缓冲区数据可能未刷新到文件,导致数据丢失;资源占用:流对象占用的内存、CPU 资源无法释放,长期运行会导致内存泄漏,系统性能下降;文件锁定:部分操作系统中,未关闭的流会锁定文件,导致其他程序无法修改或删除该文件;
最佳实践:使用 try-with-resources 语法(Java 7+),自动关闭实现 AutoCloseable 接口的流对象,避免手动关闭遗漏。
Redis 常见数据类型、用途及是否搭建过 Redis 集群
答:
一、Redis 常见数据类型及用途:
| 数据类型 | 核心特点 | 典型用途 |
|---|---|---|
| String(字符串) | 二进制安全,支持字符串、数字、二进制数据 | 缓存(如用户信息)、计数器(如文章阅读量)、分布式锁(setnx) |
| Hash(哈希) | 键值对集合,适合存储对象 | 缓存对象(如用户信息:id→{name: “张三”, age: 20}) |
| List(列表) | 有序链表,支持两端插入/删除 | 消息队列(lpush + rpop)、排行榜(lrange)、最新消息列表 |
| Set(集合) | 无序集合,支持交集、并集、差集 | 好友关系(共同好友)、去重(如用户标签去重)、抽奖活动 |
| ZSet(有序集合) | 有序(按 score 排序),支持范围查询 | 排行榜(如销量排行、积分排行)、延迟队列(score 为时间戳) |
| Bitmap(位图) | 按位存储,节省空间 | 签到统计(如用户每月签到情况)、状态标记(如是否在线) |
| HyperLogLog(基数统计) | 估算集合中不重复元素个数,占用空间小 | UV 统计(如网站独立访客数) |
二、Redis 集群相关:
若未搭建过,可按以下逻辑回答:
了解 Redis 集群的核心模式:
主从复制:1 主 N 从,主库写入,从库同步数据并提供读服务,提升读性能和容灾能力;哨兵模式(Sentinel):监控主从节点,主库故障时自动切换从库为新主库,实现高可用;Redis Cluster:分片集群,将数据分散到多个节点(默认 16384 个槽位),支持水平扩展(增加节点提升容量);
未实际搭建过,但通过官方文档和 Docker 模拟过主从复制和哨兵模式,理解其核心原理(如主从同步的 RDB 全量复制+AOF 增量复制,哨兵的选举机制);若后续工作需要,可快速通过官方工具(redis-cli)或运维工具(如 RedisInsight)搭建集群,重点关注槽位分配、数据迁移、故障转移等核心环节。
String 可不可以被继承?为什么
答:不可以。原因是 Java 中的 String 类被 关键字修饰,而
final 修饰的类具有“不可继承性”。
final
核心细节:
String 类定义:;final 类的特性:被 final 修饰的类不能被其他类继承,其方法也默认是 final(无法重写);设计目的:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
保证 String 的不可变性:String 是不可变对象(创建后内容无法修改),若允许继承,子类可能重写方法破坏不可变性(如修改 value 数组);安全性:String 广泛用于存储敏感数据(如密码、会话标识),不可继承可避免子类恶意篡改;性能优化:String 不可变,可被缓存(如字符串常量池),提升查询效率,若允许继承,缓存机制会失效。
补充:若需扩展 String 的功能,可通过“组合”而非“继承”(如创建工具类 StringUtils,提供静态方法)。
MySQL 中使用 in 会不会造成索引失效?为什么
答:不一定,in 关键字是否导致索引失效,取决于 后面的元素数量和查询条件的具体情况:
in
索引生效场景:
当 后面的元素数量较少(如 10 个以内),MySQL 优化器会选择使用索引,通过索引快速定位匹配的记录;示例:
in(id 为主键索引),会使用索引,效率很高;
SELECT * FROM user WHERE id IN (1,2,3,4)
索引失效场景:
当 后面的元素数量过多(如 1000+ 个),MySQL 优化器会认为“索引遍历的成本高于全表扫描”,选择全表扫描,导致索引失效;当
in 配合非索引字段或函数操作时,索引失效(如
in,create_time 有索引但被函数操作,索引失效);
SELECT * FROM user WHERE DATE(create_time) IN ('2024-01-01', '2024-01-02')
底层原因:
MySQL 优化器会根据“数据分布”和“查询成本”动态选择执行计划: 本质是多个
in 条件的集合,元素数量少时,索引能快速过滤;数量多时,索引需要多次查找,成本高于全表扫描;
OR
优化建议:
当 元素数量过多时,改用
in 查询(如
JOIN);确保
SELECT * FROM user u JOIN (SELECT 1 AS id UNION ALL SELECT 2 ...) t ON u.id = t.id 后面的字段是索引字段,且避免函数操作。
in
Redis 的持久化方式有哪些?有什么区别
答:Redis 持久化用于“将内存中的数据保存到磁盘,避免重启后数据丢失”,核心有两种方式:RDB 和 AOF。
RDB(Redis Database,快照持久化):
核心原理:在指定时间间隔内,拍摄内存数据的“快照”,保存为 .rdb 二进制文件(如 dump.rdb);触发方式:
手动触发:执行 (同步,阻塞 Redis 服务)或
save(异步,fork 子进程执行,不阻塞);自动触发:通过配置文件设置(如
bgsave 表示 900 秒内至少 1 个键变化,自动执行 bgsave);
save 900 1
优点:文件体积小,恢复速度快(直接加载到内存),对性能影响小(异步触发时);缺点:数据安全性低,可能丢失两次快照间的数据(如 Redis 意外宕机);
AOF(Append Only File,日志持久化):
核心原理:记录每一条写命令(如 set、hset),追加到 .aof 文件中,Redis 重启时重新执行文件中的命令恢复数据;触发方式:通过配置文件开启(),支持三种同步策略:
appendonly yes
:每条命令同步写入磁盘,数据最安全,性能最差;
appendfsync always:每秒同步一次,平衡安全性和性能(默认);
appendfsync everysec:由操作系统决定同步时机,性能最好,数据安全性最低;
appendfsync no
优点:数据安全性高,丢失数据量少(最多丢失 1 秒数据);缺点:文件体积大(命令日志重复且冗余),恢复速度慢(需重新执行所有命令),写性能略低于 RDB;
两者区别总结:
| 特性 | RDB | AOF |
|---|---|---|
| 数据安全性 | 低(丢失两次快照间数据) | 高(最多丢失 1 秒数据) |
| 文件体积 | 小(二进制压缩存储) | 大(文本命令日志) |
| 恢复速度 | 快 | 慢 |
| 写性能影响 | 小(异步触发) | 中(同步策略影响) |
| 适用场景 | 备份、快速恢复、数据一致性要求低 | 数据安全性要求高(如金融、电商) |
最佳实践:Redis 5.0+ 支持“RDB + AOF 混合持久化”,AOF 文件中包含 RDB 快照和后续命令日志,兼顾 RDB 的快速恢复和 AOF 的高安全性。
对面试公司/部门的疑问(如部门职能、面试流程等)
答:面试结尾的反问环节,建议问“体现求职诚意、关注个人发展和工作内容”的问题,避免问薪资、福利(HR 面再问),示例如下:
业务相关:贵部门的核心业务是什么?目前在技术上面临的最大挑战是什么?
岗位相关:这个岗位的日常工作内容主要包括哪些?是否需要参与跨团队协作(如与产品、运维团队)?
技术相关:团队目前使用的技术栈是什么?是否有技术分享、培训或开源贡献的机会?
发展相关:这个岗位的晋升路径是怎样的?团队对新人的培养计划是什么?
流程相关:后续的面试流程是怎样的?大概需要多长时间能收到下一轮面试通知?
核心原则:问题要具体、有价值,避免泛泛而谈(如“贵公司怎么样?”),同时展现对岗位和团队的兴趣。
