1月21日面试题整理 Java后端及自动化测试面试相关 偏简单
一、 Java 基础部分
1. Java的反射机制介绍一下
定义:
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
核心类:
Class:表示正在运行的Java应用程序中的类和接口。Constructor:提供关于类的单个构造方法的信息以及对它的访问权限。Method:提供关于类或接口上单独某个方法的信息。Field:提供有关类或接口的单个字段的信息。
原理:
程序运行时,JVM 会把编译后的.class文件(字节码)加载到内存中,并为每个类创建一个唯一的Class对象(这个对象存储了该类的所有元信息:类名、父类、接口、属性、方法、构造器等)。
优缺点:
- 优点:极大的灵活性,可以在运行时动态加载类,提高代码的通用性(比如框架设计)。
- 缺点:性能开销大(解释执行,慢于直接代码调用);破坏封装性(可以访问private属性);安全问题。
2. 平常项目里反射机制哪里用到了?
在业务开发中直接写反射代码可能不多,但我们使用的框架无处不在:
- Spring IOC/DI:Spring通过读取XML或注解配置,获取类名,然后通过反射
Class.forName()加载类并实例化对象(Bean),再通过反射注入依赖。 - Spring AOP:动态代理(JDK动态代理)底层就是反射,用于实现事务管理、日志切面。
- JDBC:
Class.forName("com.mysql.cj.jdbc.Driver")加载数据库驱动。 - ORM框架 (MyBatis/Hibernate):查询数据库结果后,通过反射将ResultSet映射到Java实体类(POJO)的属性中。
- JSON序列化工具 (Jackson/Gson):将对象转换为JSON字符串时,通过反射读取对象的所有属性值。
3. 异常里面的运行时异常和非运行时异常
Java异常的根类是 Throwable,分为 Error (错误,无法恢复) 和 Exception。Exception 分为两类:
- 非运行时异常 (Checked Exception / 受检异常):
-
- 定义:编译器强制要求处理的异常。如果代码中可能抛出这类异常,必须使用
try-catch捕获或者throws抛出,否则编译不通过。 - 场景:通常是外部环境导致的,不可控。
- 例子:
IOException(文件读写),SQLException(数据库),ClassNotFoundException。
- 定义:编译器强制要求处理的异常。如果代码中可能抛出这类异常,必须使用
- 运行时异常 (Unchecked Exception / Runtime Exception):
-
- 定义:继承自
RuntimeException。编译器不强制要求处理。 - 场景:通常是程序员的代码逻辑错误,应该通过修改代码来避免。
- 例子:
- 定义:继承自
-
-
NullPointerException(空指针)IndexOutOfBoundsException(数组越界)ClassCastException(类型转换错误)ArithmeticException(除以0)
-
二、 数据库 (MySQL) 与 缓存 (Redis)
4. 怎么保证 Redis 和 MySQL 的数据一致性?
这是一个经典的分布式数据一致性问题。
常见的策略(Cache Aside Pattern 旁路缓存模式):
- 读数据:先读缓存 -> 缓存没有 -> 读数据库 -> 将数据写入缓存 -> 返回。
- 写数据:先更新数据库,然后删除缓存。
为什么是删除缓存而不是更新缓存?
- 懒加载:如果写操作频繁,更新缓存会计算很多次,但可能没人读,浪费性能。删除后,下一次读请求自然会回填最新的。
- 并发问题:两个线程同时更新,A先更DB,B后更DB;但可能B先更缓存,A后更缓存,导致脏数据。
终极方案(保证强一致性或最终一致性):
- 延时双删(见下文详解)。
- 消息队列 + 重试:更新DB后,发送消息到MQ,由消费者去删除Redis。如果删除失败,不断重试。
- 订阅 Binlog (如 Canal):程序只修DB,Canal监听MySQL Binlog变化,解析后异步去同步/删除Redis数据。
5. MySQL的索引介绍一下
定义:索引是帮助MySQL高效获取数据的数据结构。
底层结构:主要是 B+ Tree。
常见分类:
- 聚簇索引 (Clustered Index):通常是主键,叶子节点存储完整的数据行。
- 非聚簇索引 (Secondary Index):叶子节点存储主键的值。查询时通常需要“回表”(先查到主键,再回主键索引查数据)。
- 唯一索引:列值必须唯一。
- 联合索引:多个列组成的索引,遵循最左前缀匹配原则。
6. MySQL索引它本身存在的一些问题
虽然索引快,但不能滥用:
- 空间占用:索引文件需要占用物理磁盘空间。
- 降低写性能:对表进行
INSERT,UPDATE,DELETE时,MySQL不仅要更新数据,还要调整索引结构(页分裂、重平衡),导致写变慢。 - 维护成本:随着数据量增加,索引可能会碎片化,需要定期优化。
- 失效风险:写得不好的SQL(如对索引字段做运算、使用
!=、LIKE '%abc')会导致索引失效,退化为全表扫描。
7. MySQL的分区了解吗?
定义:将一个大的物理表,在逻辑上保持为一个表,但在物理磁盘上分割成多个小文件存储。
类型:
RANGE:基于属于一个给定连续区间的列值(如按年份分区)。LIST:基于列值匹配一个离散值集合(如按地区分区:北上广)。HASH/KEY:基于用户定义的表达式的返回值。
作用:- 提高查询效率(分区裁剪,只查特定分区)。
- 方便维护(可以直接删掉一个旧的时间分区来清理历史数据)。
- 注意:现在的互联网架构中,更倾向于在应用层做分库分表(Sharding),而不是依赖MySQL自带的分区功能。
8. 了解过ES数据库吗,是干嘛的?
ES 的底层还包含列存式的文档存储结构,文档以 JSON 格式存储,会被序列化为二进制格式后存入磁盘,同时会为常用字段建立正排索引(文档 ID→字段值),用于聚合、排序等操作;为了提升检索性能,ES 还会将高频访问的词项和索引数据缓存到内存(如 FieldData、Shard Request Cache),减少磁盘 IO。而倒排索引本身也做了优化,比如对词项进行字典排序,通过跳表、位图(BitSet)等数据结构快速筛选文档,比如用位图表示某个词项对应的文档集合,多个词项的组合查询可以通过位图的与、或运算快速得到结果,这也是 ES 能实现近实时检索的关键 —— 数据写入后秒级即可被检索到,因为 ES 采用了分段(Segment)写入的机制,新写入的数据先存入内存缓冲区,达到一定条件后刷盘生成新的 Segment,Segment 一旦生成就不可修改,删除 / 更新操作只是标记文档为删除,后续通过合并 Segment 的方式清理无效数据,既保证了写入性能,也避免了频繁修改索引带来的开销。
Elasticsearch (ES) 是一个基于 Lucene 的分布式、RESTful 风格的搜索和数据分析引擎。
核心作用:
- 全文检索:类似百度/谷歌,支持模糊搜索、分词搜索、高亮显示。(MySQL的
Like在大数据量下太慢)。 - 日志分析:ELK Stack (Elasticsearch + Logstash + Kibana) 是运维监控和日志分析的标配。
- 数据分析:近实时的海量数据聚合分析。
核心原理:倒排索引 (Inverted Index)。通过关键词找到文档ID,而不是通过文档遍历内容。
三、 项目与流程
9. 新需求开发大概的流程是什么?
- 需求评审:产品经理(PM)讲解需求,开发(Dev)和测试(QA)评估可行性及实现难度。
- 技术方案设计:
-
- 数据库表结构设计。
- 接口定义 (API)。
- 核心逻辑流程图。
- 设计评审(Review)。
- 排期:预估开发时间和测试时间。
- 编码开发:后端写代码,前端写页面,联调接口。
- 单元测试/自测:开发人员自己保证主流程跑通。
- 提测:部署到测试环境,移交QA。
- 测试与Bug修复:QA进行功能测试、回归测试、压力测试。
- 预发布/灰度:在准生产环境验证。
- 上线 (Deploy):部署到生产环境,观察日志和监控。
10. 印象最深的需求,具体讲一下采用什么技术,如何实现的?
(这是一个开放性问题,你需要准备一个实际案例。以下是一个高频的“秒杀/高并发”案例模板)
- 场景:电商大促的商品抢购(如秒杀)。
- 难点:瞬间流量巨大,数据库扛不住;容易超卖(卖出负数)。
- 技术与实现:
-
- Redis预减库存:商品库存提前放入Redis。请求进来先扣Redis,扣成功了才有资格去操作数据库。
- 异步削峰:抢到资格的用户,不直接写DB,而是发消息到 RabbitMQ/Kafka。
- 数据库乐观锁:消费者处理消息,扣减DB库存时带上版本号或条件
update stock = stock - 1 where id = 1 and stock > 0,防止超卖。 - 限流与降级:前端按钮置灰,后端使用Sentinel或Nginx限流。
- 结果:抗住了10倍于平时的流量,数据准确无误。
四、 缓存进阶
11. 如何保证缓存和数据库一致性
(参考第4题,此处为重复问题的深入)
核心是:Cache Aside Pattern + 补偿机制(MQ或Binlog)。
12. 为什么会延迟双删?第二次删的作用是什么?
场景:如果不延迟双删,在“先更新DB,再删缓存”的策略下,极低概率会出现问题。但在“先删缓存,再更DB”的策略下(或者读写分离延迟),问题很明显。
延迟双删流程:
- 删除缓存。
- 更新数据库。
- 休眠一会(比如500ms)。
- 再次删除缓存。
第二次删除的作用:
解决并发读写导致的脏数据。
- 过程:线程A删了缓存 -> 线程A去修DB(还没修完) -> 线程B来了,发现没缓存 -> 线程B读旧DB -> 线程B把旧数据写入缓存 -> 线程A修完DB。
- 结果:DB是新的,缓存是旧的。
- 解决:线程A休眠后(确保线程B已经把旧数据写入缓存了),再删一次。这样缓存就没有旧数据了。
13. 如何评估部署Redis应该申请多少内存资源?
- 计算数据大小:(Key的平均长度 + Value的平均长度) × 预估Key的总数量。
- 预留空间:Redis除了存数据,还有内部元数据(overhead)、复制缓冲区等。通常建议预留 30% - 40% 的冗余。
- 持久化需求:如果开启RDB快照,Fork子进程时可能需要双倍内存(Copy-on-Write),虽然不用完全翻倍,但要预留足够Buffer。
- 估算公式:
预估内存 = 数据总量 / 0.7。
14. 管理缓存的生命周期的策略是什么?
- TTL (Time To Live):给Key设置过期时间(
expire)。
-
- 热点数据:时间设长点。
- 普通数据:时间设短点,防止占内存。
- 驱逐策略 (Eviction Policy):当内存满了(maxmemory)时,Redis如何删数据。
-
allkeys-lru:从所有Key中删最少使用的(最常用)。volatile-lru:只从设置了过期时间的Key中删最少使用的。random:随机删。
- 删除机制:
-
- 惰性删除:查的时候发现过期了才删。
- 定期删除:Redis后台每隔一段时间随机抽查过期Key并删除。
五、 自动化测试
15. 接口自动化采用什么框架?
Java栈:
- RestAssured:专为REST API设计的DSL,语法优雅,功能强大。
- TestNG / JUnit:测试执行引擎,管理测试用例、断言。
- HttpClient / OkHttp:底层的HTTP调用工具。
- Allure:生成漂亮的测试报告。
Python栈(见下题)。
16. Python自动化框架接触哪些?
- Pytest:最主流的单元/接口测试框架。
- Unittest:Python标准库自带,类似Java JUnit,比较老派。
- Requests:HTTP库,用于发送接口请求。
- Selenium / Playwright:用于Web UI自动化。
- Appium:用于移动端App自动化。
17. Pytest的优势?
相比于Unittest:
- 简洁:不需要像Unittest那样必须继承类,只需写
def test_xxx():即可。 - Fixture机制:非常强大。
@pytest.fixture可以灵活控制前置(Setup)和后置(Teardown)操作,支持不同作用域(function, class, session)。 - 插件生态:拥有丰富的插件,如
pytest-xdist(并发执行)、pytest-html(报告)、pytest-rerunfailures(失败重试)。 - 参数化:
@pytest.mark.parametrize实现数据驱动测试非常方便。 - 断言:直接使用Python原生的
assert语句,不需要self.assertEqual。
六、 算法题
18. 字符串"ABCBD"打印出最长回文子字符串“BCB”
这道题是 LeetCode 5. 最长回文子串。
解题思路(中心扩散法):
回文串是中心对称的。我们可以遍历字符串,以每一个字符为中心,向两边扩散,看看能扩散多远。
- 情况1:中心是一个字符(如 "aba" 中心是 b)。
- 情况2:中心是两个字符(如 "abba" 中心是 bb)。
代码逻辑 (Python示例):
def longestPalindrome(s: str) -> str:
if not s:
return ""
start, max_len = 0, 0
def expand_around_center(left, right):
# 向两边扩散,直到不相等或越界
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
# 返回当前找到的长度,注意while结束时left和right已经越界了
return right - left - 1
for i in range(len(s)):
# 1. 单中心 (如 BCB)
len1 = expand_around_center(i, i)
# 2. 双中心 (如 ABBA)
len2 = expand_around_center(i, i + 1)
cur_len = max(len1, len2)
if cur_len > max_len:
max_len = cur_len
# 更新起点:根据中心点i和长度计算新的起点
# 如果是奇数长度,i是正中心;如果是偶数,i是左中心
start = i - (cur_len - 1) // 2
return s[start : start + max_len]
# 测试
s = "ABCBD"
print(longestPalindrome(s)) # 输出 BCB
复杂度:
- 时间复杂度:
。
- 空间复杂度:
。
更多推荐

所有评论(0)