本文整理并解析了一次后端开发面试中涉及的核心技术问题,涵盖Java基础、中间件、系统设计等多个维度,旨在为开发者提供实战参考。
一、Java基础与集合框架
1. Java集合框架有哪些主要组成部分?
Java集合框架主要分为两大接口体系:Collection 和 Map。
Collection:用于存储单一对象集合,主要子接口有 List、Set、Queue。
Map:用于存储键值对(Key-Value)映射。
2. ArrayList 和 LinkedList 的区别
- 底层数据结构:
ArrayList 基于动态数组;LinkedList 基于双向链表。
- 访问效率:
ArrayList 支持随机访问(通过索引),时间复杂度为 O(1);LinkedList 需要遍历,平均为 O(n)。
- 增删效率:在列表中间插入或删除元素时,
ArrayList 需要移动后续元素,开销较大;LinkedList 仅需修改节点指针,效率更高。
- 内存占用:
ArrayList 预留一定容量空间;LinkedList 每个元素需消耗额外空间存储前后节点指针。
3. Set 和 List 的区别
- 元素唯一性:
Set 不允许重复元素;List 允许。
- 元素顺序:
List 保证插入顺序,可通过索引访问;Set 多数实现不保证顺序(如 HashSet),但 LinkedHashSet 保证插入顺序,TreeSet 保证排序顺序。
4. Set 的底层实现
HashSet:基于 HashMap 实现,元素作为 HashMap 的 Key 存储,Value 是一个固定的 Object 对象。
TreeSet:基于 TreeMap(红黑树)实现,支持自然排序或自定义排序。
LinkedHashSet:继承自 HashSet,内部通过 LinkedHashMap 实现,维护了元素的插入顺序链表。
5. HashMap 的底层原理
HashMap 在 JDK 1.8 后采用了“数组 + 链表 / 红黑树”的结构。
- 哈希计算与定位:根据 Key 的
hashCode() 计算哈希值,与数组长度取模得到数组下标。
- 解决哈希冲突:如果多个 Key 定位到同一桶(bucket),则以链表形式存储。当链表长度超过阈值(默认为8)且数组容量大于64时,链表会转换为红黑树,以提升查询效率。
- 扩容机制:当元素数量超过
容量 * 负载因子时,数组会扩容为原来的2倍,并重新计算所有元素的位置(rehash)。更多关于Java集合框架与底层原理的深入探讨,可以查看相关专题。
6. Java内存模型(JMM)
JMM 定义了线程与主内存之间的抽象关系,规定了多线程环境下变量的访问方式(如读取、写入)。
- 主内存与工作内存:每个线程有自己的工作内存,存储共享变量的副本。所有共享变量存储在主内存中。
- 内存间交互操作:定义了
lock、unlock、read、load、use、assign、store、write 八个原子操作来规定变量如何从主内存同步到工作内存。
- 三大特性:原子性、可见性(由
volatile、synchronized 等保证)、有序性(禁止指令重排序)。
二、Redis 深度探析
7. 为什么使用 Redis?
主要用于解决关系型数据库(如 MySQL)在高并发场景下的性能瓶颈,其核心价值在于:
- 极高的性能:数据存储在内存中,读写速度极快。
- 丰富的数据结构:支持字符串、哈希、列表、集合、有序集合等,可直接实现复杂业务逻辑。
- 用途广泛:可作为缓存、分布式锁、会话存储、消息队列等。
8. Redis 的主要应用场景
- 缓存:加速热点数据的访问,减轻数据库压力。
- 分布式会话:存储用户会话信息,实现无状态服务。
- 排行榜/计数器:利用
ZSET 和 INCR 命令轻松实现。
- 消息队列:使用
List 的 LPUSH/BRPOP 或 Pub/Sub 模式。
- 分布式锁:通过
SETNX 命令实现。
9. Redis 的过期键删除策略
- 惰性删除:当客户端访问一个 Key 时,Redis 会检查其是否过期,如果过期则立即删除。
- 定期删除:Redis 默认每隔一段时间(可配置)随机抽取一部分设置了过期时间的 Key,检查并删除其中已过期的 Key。这个过程逐步进行。
10. 惰性删除可能存在的问题
如果大量过期 Key 堆积,且长时间没有被访问,它们将无法被惰性删除策略清理,导致内存被无效数据长期占用,可能引发内存浪费甚至内存溢出。
11. 定期删除为何不扫描全部 Key?
如果一次性扫描所有过期 Key,在 Key 数量巨大时,会导致线程长时间阻塞,严重影响 Redis 的服务性能。采用随机抽样部分 Key进行检查,是一种权衡,用可控的、短暂的性能开销来逐步清理过期数据。
12. Redis 持久化机制
- RDB:在指定时间间隔生成数据集的快照。文件紧凑,恢复速度快。但可能丢失最后一次快照后的数据。
- AOF:记录每一条写命令。数据丢失风险低,文件易读。但文件体积通常比 RDB 大,恢复速度慢。可以配置为每秒同步,在性能和数据安全间取得平衡。生产环境常同时开启,利用 AOF 保证数据安全,用 RDB 便于灾难恢复和快速重启。
13. Redis 内存淘汰策略
当内存使用达到 maxmemory 上限时,根据策略淘汰数据:
noeviction:不淘汰,写请求报错。
allkeys-lru:从所有 Key 中淘汰最近最少使用的。
volatile-lru:从设置了过期时间的 Key 中淘汰最近最少使用的。
allkeys-random / volatile-random:随机淘汰。
volatile-ttl:淘汰剩余生存时间最短的 Key。
14. Redis ZSET 结合项目介绍
有序集合在项目中常用于排行榜功能。
- 实现:将用户ID作为 member,得分(如积分、成绩)作为 score 存入
ZSET。
- 操作:使用
ZADD 更新分数,ZINCRBY 增加分数,ZREVRANGE 获取排名前 N 的用户。
- 优势:分数自动排序,范围查询效率高(O(log N)),并且能保证元素唯一性。
三、消息队列与系统设计
15. Kafka 如何保证顺序消费?
在 Kafka 中,顺序性保障是分区维度的。
- 生产者顺序写入:将需要保证顺序的一类消息指定相同的 Key。Kafka 根据 Key 的哈希值将其发送到同一个 Partition。一个 Partition 内的消息是有序的。
- 消费者顺序读取:一个 Partition 只能被同一个消费者组内的一个消费者消费。确保单个消费者串行处理该分区内的消息即可。需要注意避免消费者重启或重平衡导致的消息重复,这可能破坏业务上的顺序,通常需结合业务逻辑或使用幂等性来应对。
四、设计模式
16. 单例模式
确保一个类只有一个实例,并提供全局访问点。
- 关键点:私有化构造函数,静态私有实例,静态公有获取方法。
- 实现方式:饿汉式、懒汉式(双检锁)、静态内部类、枚举。对于Java高级特性与设计模式的实践,有众多应用场景。
17. 工厂模式
定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
18. 策略模式
定义一系列算法,将每个算法封装起来,并使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。
五、Linux 与算法
19. 常用的 Linux 命令
- 文件/目录操作:
ls, cd, pwd, cp, mv, rm, mkdir, find, grep
- 权限管理:
chmod, chown
- 进程管理:
ps, top, kill, netstat
- 系统监控:
df, du, free
- 文本处理:
cat, tail, head, vim, sed, awk
20. 算法题
21. 反问环节建议
面试结束时,可以向面试官询问团队正在使用的技术栈、业务方向、面临的挑战以及对新人的培养计划等,这既能展示你的主动性,也能帮助你判断岗位匹配度。
|