去哪儿网AI面试
一、数组和链表的区别?
1. 存储方式
- 数组:内存中连续存储,支持随机访问(通过索引直接访问元素),时间复杂度
O(1)。 - 链表:内存中非连续存储,通过指针连接节点,需要从头遍历才能找到指定位置,访问复杂度
O(n)。
2. 插入/删除效率
- 数组:插入或删除元素需要移动后续元素,平均时间复杂度
O(n)。 - 链表:已知节点后,可以在
O(1)时间完成插入和删除,但查找目标位置是O(n)。
3. 适用场景
- 需要频繁随机访问 → 数组更合适。
- 需要频繁插入/删除 → 链表更合适,尤其是在中间插入删除元素时。
二、Java的垃圾回收机制GC以及常见的垃圾回收器有哪些?
1. GC机制简述 Java通过垃圾回收器(GC)自动管理堆内存,主要任务:
- 标记(Mark):找出所有存活对象(从GC Roots开始可达)。
- 清除(Sweep):回收不可达对象的内存。
- 压缩(Compact):整理碎片,让内存连续,避免频繁分配失败。
2. 常见垃圾回收器
| 回收器 | 特点 | 适用场景 |
|---|---|---|
| Serial | 单线程,Stop-The-World,简单高效 | 单核或Client模式 |
| Parallel (吞吐量优先) | 多线程,追求吞吐量 | CPU核心多、批处理应用 |
| CMS (低延迟) | 并发标记清除,减少STW时间 | 响应时间敏感应用 |
| G1 | 分区回收,支持可预测的停顿时间 | 大堆、低延迟服务 |
| ZGC / Shenandoah | 超低延迟,停顿时间毫秒级 | 大内存、实时系统 |
三、对MySQL索引的理解,为什么它可以加快查询速度?
1. 索引原理 MySQL常用索引结构是 B+树索引,它能保持数据有序,并支持范围查找。通过B+树多层节点的指针,可以快速缩小查询范围,而不是全表扫描。
2. 性能提升原因
- B+树查询的时间复杂度是
O(log n),而全表扫描是O(n)。 - 叶子节点存储指向数据行的指针(或主键),可直接定位数据行。
- 索引有利于排序和分组,减少额外的排序操作。
3. 注意事项
- 合理设计索引(区分单列索引、联合索引、覆盖索引)。
- 过多索引会影响写性能(更新时需要维护索引)。
四、如何设计一个简单的日历系统,支持事件的创建、更新及提醒功能?
功能需求:
- 事件创建:用户可添加日程事件(标题、开始时间、结束时间、提醒时间)。
- 事件更新:支持修改和删除。
- 提醒功能:在指定时间发送通知。
设计思路:
- 数据库表设计(MySQL)
CREATE TABLE event (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
title VARCHAR(100) NOT NULL,
start_time DATETIME NOT NULL,
end_time DATETIME,
remind_time DATETIME,
status TINYINT DEFAULT 1, -- 1正常 0删除
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
- 业务流程
- 创建/更新:写入数据库,并写入消息队列(用于提醒)。
- 提醒功能:定时任务扫描即将到达提醒时间的事件,推送到MQ,由消费者异步发送通知(如邮件/短信/APP推送)。
消息队列具体会选择什么工具实现?为什么选择它?
推荐使用 RabbitMQ / Kafka:
- RabbitMQ:可靠性好,支持延迟队列,适合实时提醒。
- Kafka:吞吐量大,适合高并发日志类场景。 对于提醒系统,RabbitMQ 更合适,可直接使用延迟插件或死信队列实现定时提醒。
如果在这个系统中事件提醒的时间点非常密集,比如每秒都有大量提醒需要发送,你会如何优化消息队列的处理效率?
批量消费:消费者批量拉取消息,批量推送提醒,减少IO开销。
水平扩展:增加消费者实例,通过队列分区提高吞吐量。
异步推送:使用线程池、异步HTTP请求,提高并发处理能力。
降级与限流:当提醒量超出系统承载能力,可对低优先级提醒降级(如延迟几秒)。
缓存+预取:提前把未来几分钟的提醒加载到Redis,减少数据库查询压力。