小米研发服务器工程师面经
- 作者简介:一名后端开发人员,每天分享后端开发以及人工智能相关技术,行业前沿信息,面试宝典。
- 座右铭:未来是不可确定的,慢慢来是最快的。
- 个人主页:极客李华-CSDN博客
- 合作方式:私聊+
- 这个专栏内容:BAT等大厂常见后端java开发面试题详细讲解,更新数目100道常见大厂java后端开发面试题。
- 我的CSDN社区:https://bbs.csdn.net/forums/99eb3042821a4432868bb5bfc4d513a8
- 微信公众号,抖音,b站等平台统一叫做:极客李华,加入微信公众号领取各种编程资料,加入抖音,b站学习面试技巧
一面
1. ==
和 equals
的区别
==
:- 比较的是引用是否相同(对于对象),或者值是否相等(对于基本数据类型)。
- 对于引用类型,
==
判断两个引用是否指向同一个内存地址。
equals
:- 是
Object
类中的方法,默认行为和==
一致,但可以被重写。 - 通常用于比较对象的内容是否相同。
- 例如:
String
类重写了equals
方法,比较的是字符串的内容,而不是内存地址。
- 是
2. 重写 equals
的过程中需要注意的事情
- 必须满足以下特性:
- 自反性:
x.equals(x)
为 true。 - 对称性:
x.equals(y)
为 true,则y.equals(x)
也为 true。 - 传递性:
x.equals(y)
和y.equals(z)
为 true,则x.equals(z)
也为 true。 - 一致性:多次调用
x.equals(y)
结果始终一致,前提是对象未被修改。 - 与
hashCode
一致:如果两个对象equals
返回 true,则其hashCode
也必须相等。
- 自反性:
- 重写
hashCode
:重写equals
时应同步重写hashCode
方法,以确保HashMap
等集合的正确性。
3. 代码示例分析
String a = new String("111");
String b = new String("111");
System.out.println(a == b); // 输出 false
- 原因:
new String("111")
会创建新的对象,a
和b
是两个不同的对象,==
比较的是引用地址。
String a = "111";
String b = "111";
System.out.println(a == b); // 输出 true
- 原因:字符串常量池中的相同字符串只会存储一份,
a
和b
指向同一个常量池地址。
4. HashMap 的基本结构
-
基本结构:
- HashMap 是基于数组 + 链表 + 红黑树实现的。
- 通过哈希函数将键值对映射到数组的某个索引位置,数组存储的是链表(JDK1.8 之前)或红黑树(JDK1.8 之后)。
- 当链表长度超过阈值(默认 8)时,会转化为红黑树,优化查询效率。
-
存储原理:
- 调用
hashCode
方法计算键的哈希值,再通过(n - 1) & hash
计算出数组索引。 - 解决哈希冲突的方法是拉链法。
- 调用
5. HashMap 是否可以存放空值?频繁 put
null 会怎样?
-
可以放空值:
- 键
null
只会被映射到数组的第一个位置(索引 0)。 - 值
null
无限制,可以存储任意多个。
- 键
-
频繁
put
null:- 如果频繁对
null
键put
值,只会覆盖之前的值,不会产生冲突或其他问题。
- 如果频繁对
6. ArrayList
和 LinkedList
的区别
-
主要区别:
ArrayList
是基于动态数组实现,随机访问性能更好,但插入和删除元素(尤其中间位置)性能较差。LinkedList
是基于双向链表实现,插入和删除性能较好,但随机访问性能较差。
-
线程安全性:
- 都不是线程安全的。如果需要线程安全,可以使用
Collections.synchronizedList
或CopyOnWriteArrayList
。
- 都不是线程安全的。如果需要线程安全,可以使用
7. 多线程下访问一个 List
应该使用哪一个?
- 推荐使用
CopyOnWriteArrayList
:- 线程安全,适合读多写少的场景。
- 采用写时复制的策略,每次修改都会创建一个新的副本。
8. concurrent
包的了解及实际应用
-
常见组件:
ConcurrentHashMap
:线程安全的哈希表,分段锁机制提高并发性能。CopyOnWriteArrayList
:适合读多写少的场景。BlockingQueue
:线程安全的队列,用于生产者-消费者模型。ExecutorService
:线程池框架。
-
实际应用:
- 线程池(
ThreadPoolExecutor
)处理高并发任务。 - 使用
ConcurrentHashMap
作为缓存。 - 使用
BlockingQueue
实现任务队列。
- 线程池(
9. 线程池的默认拒绝策略
- 默认拒绝策略是
AbortPolicy
:- 当线程池满了并且队列也满了时,会抛出
RejectedExecutionException
。
- 当线程池满了并且队列也满了时,会抛出
10. FixedThreadPool
的默认队列长度及潜在问题
- 默认使用
LinkedBlockingQueue
,队列长度为无界。 - 问题:
- 如果任务量过大,队列可能无限增长,导致内存溢出(OOM)。
11. 如何完成线程的并行等待?
- 使用
CountDownLatch
或CyclicBarrier
:CountDownLatch
:用于等待一组线程完成任务。CyclicBarrier
:用于一组线程相互等待,直到全部到达屏障点。
12. 字节流和字符流的区别
- 字节流:以 字节 为单位(
InputStream
和OutputStream
)。 - 字符流:以 字符 为单位(
Reader
和Writer
)。 - 区别:
- 字节流适合处理二进制数据(如图片、视频)。
- 字符流适合处理文本数据,支持编码转换。
13. 如何实现断点续传?
-
实现步骤:
- 文件切分:根据分块大小切分文件,记录每个分块的开始和结束位置。
- 记录断点信息:使用文件或数据库记录已传输的分块位置。
- 多线程下载:通过多线程并发下载分块。
- 合并文件:下载完成后,将分块拼接为完整文件。
-
关键技术:
RandomAccessFile
读取和写入指定文件位置。
14. Spring 的相关问题
-
Spring 的基本特性:
- 提供了 IOC(控制反转)和 AOP(面向切面编程)机制。
- 管理单例 Bean,支持事务管理和多种集成。
-
Spring 如何解决循环依赖:
- 通过 三级缓存(单例池、提前暴露的对象和正在创建中的 Bean)实现。
- 在实例化和初始化过程中暴露一个早期代理对象。
-
Spring 的事务管理:
- 使用
@Transactional
注解声明事务,基于 AOP 实现事务控制。 - 默认传播机制是
REQUIRED
。
- 使用
-
@Transactional
注解失效原因:- 方法未被代理(如同类方法调用)。
- 事务配置错误。
- 事务注解未生效。
-
事务传播机制:
- 例如:可以使用
REQUIRES_NEW
创建一个新事务,独立于外部事务。
- 例如:可以使用
15. 数据库隔离级别
- 隔离级别(由低到高):
- READ UNCOMMITTED:可能出现脏读。
- READ COMMITTED:避免脏读,但可能出现不可重复读。
- REPEATABLE READ:避免脏读和不可重复读,但可能出现幻读。
- SERIALIZABLE:完全避免并发问题,但性能较差。