一、 Java 基础部分

1. Java的反射机制介绍一下

定义
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

核心类

  • Class:表示正在运行的Java应用程序中的类和接口。
  • Constructor:提供关于类的单个构造方法的信息以及对它的访问权限。
  • Method:提供关于类或接口上单独某个方法的信息。
  • Field:提供有关类或接口的单个字段的信息。

原理:

程序运行时,JVM 会把编译后的.class文件(字节码)加载到内存中,并为每个类创建一个唯一的Class对象(这个对象存储了该类的所有元信息:类名、父类、接口、属性、方法、构造器等)。

优缺点

  • 优点:极大的灵活性,可以在运行时动态加载类,提高代码的通用性(比如框架设计)。
  • 缺点:性能开销大(解释执行,慢于直接代码调用);破坏封装性(可以访问private属性);安全问题。

2. 平常项目里反射机制哪里用到了?

在业务开发中直接写反射代码可能不多,但我们使用的框架无处不在:

  1. Spring IOC/DI:Spring通过读取XML或注解配置,获取类名,然后通过反射Class.forName()加载类并实例化对象(Bean),再通过反射注入依赖。
  2. Spring AOP:动态代理(JDK动态代理)底层就是反射,用于实现事务管理、日志切面。
  3. JDBCClass.forName("com.mysql.cj.jdbc.Driver") 加载数据库驱动。
  4. ORM框架 (MyBatis/Hibernate):查询数据库结果后,通过反射将ResultSet映射到Java实体类(POJO)的属性中。
  5. JSON序列化工具 (Jackson/Gson):将对象转换为JSON字符串时,通过反射读取对象的所有属性值。

3. 异常里面的运行时异常和非运行时异常

Java异常的根类是 Throwable,分为 Error (错误,无法恢复) 和 ExceptionException 分为两类:

  1. 非运行时异常 (Checked Exception / 受检异常)
    • 定义:编译器强制要求处理的异常。如果代码中可能抛出这类异常,必须使用 try-catch 捕获或者 throws 抛出,否则编译不通过。
    • 场景:通常是外部环境导致的,不可控。
    • 例子IOException (文件读写), SQLException (数据库), ClassNotFoundException
  1. 运行时异常 (Unchecked Exception / Runtime Exception)
    • 定义:继承自 RuntimeException。编译器不强制要求处理。
    • 场景:通常是程序员的代码逻辑错误,应该通过修改代码来避免。
    • 例子
      • NullPointerException (空指针)
      • IndexOutOfBoundsException (数组越界)
      • ClassCastException (类型转换错误)
      • ArithmeticException (除以0)

二、 数据库 (MySQL) 与 缓存 (Redis)

4. 怎么保证 Redis 和 MySQL 的数据一致性?

这是一个经典的分布式数据一致性问题。

常见的策略(Cache Aside Pattern 旁路缓存模式)

  • 读数据:先读缓存 -> 缓存没有 -> 读数据库 -> 将数据写入缓存 -> 返回。
  • 写数据先更新数据库,然后删除缓存

为什么是删除缓存而不是更新缓存?

  • 懒加载:如果写操作频繁,更新缓存会计算很多次,但可能没人读,浪费性能。删除后,下一次读请求自然会回填最新的。
  • 并发问题:两个线程同时更新,A先更DB,B后更DB;但可能B先更缓存,A后更缓存,导致脏数据。

终极方案(保证强一致性或最终一致性)

  1. 延时双删(见下文详解)。
  2. 消息队列 + 重试:更新DB后,发送消息到MQ,由消费者去删除Redis。如果删除失败,不断重试。
  3. 订阅 Binlog (如 Canal):程序只修DB,Canal监听MySQL Binlog变化,解析后异步去同步/删除Redis数据。

5. MySQL的索引介绍一下

定义:索引是帮助MySQL高效获取数据的数据结构。
底层结构:主要是 B+ Tree
常见分类

  • 聚簇索引 (Clustered Index):通常是主键,叶子节点存储完整的数据行。
  • 非聚簇索引 (Secondary Index):叶子节点存储主键的值。查询时通常需要“回表”(先查到主键,再回主键索引查数据)。
  • 唯一索引:列值必须唯一。
  • 联合索引:多个列组成的索引,遵循最左前缀匹配原则

6. MySQL索引它本身存在的一些问题

虽然索引快,但不能滥用:

  1. 空间占用:索引文件需要占用物理磁盘空间。
  2. 降低写性能:对表进行 INSERT, UPDATE, DELETE 时,MySQL不仅要更新数据,还要调整索引结构(页分裂、重平衡),导致写变慢。
  3. 维护成本:随着数据量增加,索引可能会碎片化,需要定期优化。
  4. 失效风险:写得不好的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 风格的搜索和数据分析引擎。
核心作用

  1. 全文检索:类似百度/谷歌,支持模糊搜索、分词搜索、高亮显示。(MySQL的Like在大数据量下太慢)。
  2. 日志分析:ELK Stack (Elasticsearch + Logstash + Kibana) 是运维监控和日志分析的标配。
  3. 数据分析:近实时的海量数据聚合分析。
    核心原理倒排索引 (Inverted Index)。通过关键词找到文档ID,而不是通过文档遍历内容。

三、 项目与流程

9. 新需求开发大概的流程是什么?

  1. 需求评审:产品经理(PM)讲解需求,开发(Dev)和测试(QA)评估可行性及实现难度。
  2. 技术方案设计
    • 数据库表结构设计。
    • 接口定义 (API)。
    • 核心逻辑流程图。
    • 设计评审(Review)。
  1. 排期:预估开发时间和测试时间。
  2. 编码开发:后端写代码,前端写页面,联调接口。
  3. 单元测试/自测:开发人员自己保证主流程跑通。
  4. 提测:部署到测试环境,移交QA。
  5. 测试与Bug修复:QA进行功能测试、回归测试、压力测试。
  6. 预发布/灰度:在准生产环境验证。
  7. 上线 (Deploy):部署到生产环境,观察日志和监控。

10. 印象最深的需求,具体讲一下采用什么技术,如何实现的?

(这是一个开放性问题,你需要准备一个实际案例。以下是一个高频的“秒杀/高并发”案例模板)

  • 场景:电商大促的商品抢购(如秒杀)。
  • 难点:瞬间流量巨大,数据库扛不住;容易超卖(卖出负数)。
  • 技术与实现
    1. Redis预减库存:商品库存提前放入Redis。请求进来先扣Redis,扣成功了才有资格去操作数据库。
    2. 异步削峰:抢到资格的用户,不直接写DB,而是发消息到 RabbitMQ/Kafka
    3. 数据库乐观锁:消费者处理消息,扣减DB库存时带上版本号或条件 update stock = stock - 1 where id = 1 and stock > 0,防止超卖。
    4. 限流与降级:前端按钮置灰,后端使用Sentinel或Nginx限流。
  • 结果:抗住了10倍于平时的流量,数据准确无误。

四、 缓存进阶

11. 如何保证缓存和数据库一致性

(参考第4题,此处为重复问题的深入)
核心是:Cache Aside Pattern + 补偿机制(MQ或Binlog)

12. 为什么会延迟双删?第二次删的作用是什么?

场景:如果不延迟双删,在“先更新DB,再删缓存”的策略下,极低概率会出现问题。但在“先删缓存,再更DB”的策略下(或者读写分离延迟),问题很明显。

延迟双删流程

  1. 删除缓存。
  2. 更新数据库。
  3. 休眠一会(比如500ms)。
  4. 再次删除缓存。

第二次删除的作用
解决并发读写导致的脏数据

  • 过程:线程A删了缓存 -> 线程A去修DB(还没修完) -> 线程B来了,发现没缓存 -> 线程B读旧DB -> 线程B把旧数据写入缓存 -> 线程A修完DB。
  • 结果:DB是新的,缓存是旧的。
  • 解决:线程A休眠后(确保线程B已经把旧数据写入缓存了),再删一次。这样缓存就没有旧数据了。

13. 如何评估部署Redis应该申请多少内存资源?

  1. 计算数据大小:(Key的平均长度 + Value的平均长度) × 预估Key的总数量。
  2. 预留空间:Redis除了存数据,还有内部元数据(overhead)、复制缓冲区等。通常建议预留 30% - 40% 的冗余。
  3. 持久化需求:如果开启RDB快照,Fork子进程时可能需要双倍内存(Copy-on-Write),虽然不用完全翻倍,但要预留足够Buffer。
  4. 估算公式预估内存 = 数据总量 / 0.7

14. 管理缓存的生命周期的策略是什么?

  1. TTL (Time To Live):给Key设置过期时间(expire)。
    • 热点数据:时间设长点。
    • 普通数据:时间设短点,防止占内存。
  1. 驱逐策略 (Eviction Policy):当内存满了(maxmemory)时,Redis如何删数据。
    • allkeys-lru:从所有Key中删最少使用的(最常用)。
    • volatile-lru:只从设置了过期时间的Key中删最少使用的。
    • random:随机删。
  1. 删除机制
    • 惰性删除:查的时候发现过期了才删。
    • 定期删除:Redis后台每隔一段时间随机抽查过期Key并删除。

五、 自动化测试

15. 接口自动化采用什么框架?

Java栈

  • RestAssured:专为REST API设计的DSL,语法优雅,功能强大。
  • TestNG / JUnit:测试执行引擎,管理测试用例、断言。
  • HttpClient / OkHttp:底层的HTTP调用工具。
  • Allure:生成漂亮的测试报告。

Python栈(见下题)。

16. Python自动化框架接触哪些?

  1. Pytest:最主流的单元/接口测试框架。
  2. Unittest:Python标准库自带,类似Java JUnit,比较老派。
  3. Requests:HTTP库,用于发送接口请求。
  4. Selenium / Playwright:用于Web UI自动化。
  5. Appium:用于移动端App自动化。

17. Pytest的优势?

相比于Unittest:

  1. 简洁:不需要像Unittest那样必须继承类,只需写 def test_xxx(): 即可。
  2. Fixture机制:非常强大。@pytest.fixture 可以灵活控制前置(Setup)和后置(Teardown)操作,支持不同作用域(function, class, session)。
  3. 插件生态:拥有丰富的插件,如 pytest-xdist (并发执行)、pytest-html (报告)、pytest-rerunfailures (失败重试)。
  4. 参数化@pytest.mark.parametrize 实现数据驱动测试非常方便。
  5. 断言:直接使用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

复杂度

  • 时间复杂度:

  • 空间复杂度:

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐