面试中自己挖的一些坑
一些面试的细节深度持续更新。。。
- 1. 这里有4题,单独写成了博客
- 2. 经典的八股文之一 (ArrayList扩容原理)
- 1.博主的回答
- 2.面试官问的一些细节
- 3.经典的八股文之一 (HashMap扩容原理)
- 1.博主的回答
- 2.面试官问的一些细节
- 4.SpringBoot的启动原理
- 1. 博主回答
- 2. 面试官问的一些细节
1. 博主刚入行时主流版本是1.8
2. 这里又被面试官给虐了一次,说1.8版本不专业,应该说是major52版本
1. 这里有4题,单独写成了博客
这里有针对数组和链表的进一步拓展: 又是被虐的一次面试
2. 经典的八股文之一 (ArrayList扩容原理)
1.博主的回答
- ArrayList 是一个数组结构的存储容器,默认情况下,数组的长度是 10. 当然我们也可以在构建 ArrayList 对象的时候自己指定初始长度
- 随着在程序里面不断地往 ArrayList 中添加数据,当添加的数据达到 10 个的时候,
ArrayList 就没有多余容量可以存储后续的数据 - 这个时候 ArrayList 会自动触发扩容
- 首先,创建一个新的数组,这个新数组的长度是原来数组长度的 1.5 倍
- 然后使用 Arrays.copyOf 方法把老数组里面的数据拷贝到新的数组里面##
扩容完成后再把当前要添加的元素加入新的数组里面,从而完成动态扩容的过程 - ArrayList最大可以扩容为,若最后一次扩容比Integer. MAX_VALUE大则为Integer. MAX_VALUE,否则为Integer. MAX_VALUE - 8
2.面试官问的一些细节
细节1:ArrayList在构建时长度就时为0,添加第一个元素时才会扩容至10
源码追溯
- 根据上述代码中的解释,new ArrayList()时,构造一个初始容量为10的空列表
细节2:ArrayList扩容最大为内存的没有 Integer. MAX_VALUE - 8 的操作
源码追溯
- jdk1.8中有Integer. MAX_VALUE - 8
- jdk11中也有Integer. MAX_VALUE - 8
- jdk17中确实没有Integer. MAX_VALUE - 8
- 从jdk的发布时间来看,可能博主刚入行java时,这位面试官就参与了jdk的编写
3.经典的八股文之一 (HashMap扩容原理)
1.博主的回答
- 初始化过程
- 数组初始化:HashMap 的底层是一个数组,默认情况下,数组的初始大小为 16
- 负载因子:负载因子默认为 0.75,用于控制何时触发扩容
- 阈值计算:阈值= 容量 × 负载因子,默认为 16 × 0.75 = 12
- 添加一个元素,调用 put(key, value) 方法时,HashMap 会执行以下步骤:
- 计算哈希值:HashMap 使用 hash(key) 方法计算键的哈希值
- 定位数组索引:根据哈希值和当前数组长度,计算出该键值对应该存储的数组索引
- 空桶:如果该索引位置为空,直接将键值对存储为一个链表节点
- 冲突处理:如果该索引位置已经有元素(链表或红黑树),则需要进一步处理冲突
- 链表:将新节点插入链表尾部
- 红黑树:如果该索引位置是红黑树,按照红黑树的插入规则插入节点
- 更新 size:键值对数量加一
- 检查阈值:如果 size 达到阈值(默认 12),触发扩容
- 当链表长度超过阈值(默认为 8)时,链表会被转换为红黑树
- 触发扩容
- 触发条件:当前键值对数量达到阈值
- 计算新容量:新容量通常是当前容量的2倍
2.面试官问的一些细节
- 面试官的反应:又是背的八股文。。。。。。。。。。
细节1:链表长度为8并且数组长度为64时,是先扩容还是先转红黑树
源码追溯
- putVal() 方法:在插入新节点时,若链表长度达到阈值,会调用 treeifyBin()
- treeifyBin() 方法:在 treeifyBin() 中,检查数组长度是否足够
- 结论
- 当链表长度 = 8 且数组长度 = 64
- 满足 binCount >= 7(链表长度为 8)且 n >= 64
- 直接执行 treeify(tab),将链表转为红黑树
- 当链表长度 = 8 但数组长度 < 64
- 优先调用 resize() 扩容,而不是转树
- 当链表长度 = 8 且数组长度 = 64
细节2:扩容和红黑树转换,哪个对性能影响更大?
-
扩容对性能的影响
- 扩容操作发生在 HashMap 的键值对数量超过当前容量与负载因子的乘积时。扩容的主要步骤包括:
- 创建一个容量为当前容量两倍的新数组
- 遍历原数组中的所有元素,并重新计算它们在新数组中的位置
- 更新 HashMap 的底层数组和其他相关属性
- 扩容操作的时间复杂度为 O(n),其中 n 是 HashMap 中的元素数量。这意味着扩容会导致 HashMap 在短时间内性能下降,尤其是在数据量较大时。扩容的目的是通过增加容量来减少哈希冲突,从而提高后续操作的效率
- 扩容操作发生在 HashMap 的键值对数量超过当前容量与负载因子的乘积时。扩容的主要步骤包括:
-
红黑树转换对性能的影响
- 红黑树转换发生在某个桶的链表长度超过 8 且数组长度大于等于 64 时。转换过程包括:
- 遍历链表,将链表节点转换为红黑树节点。
- 构建红黑树结构
- 红黑树转换的时间复杂度为 O(n),其中 n 是链表的长度。虽然红黑树转换只作用于单个链表,但它可以显著提高该链表的查询效率,将最坏情况下的时间复杂度从 O(n) 降低到 O(log n)。这种优化对于处理大量数据或哈希冲突较多的场景非常有效
- 红黑树转换发生在某个桶的链表长度超过 8 且数组长度大于等于 64 时。转换过程包括:
-
性能影响对比
- 扩容:对整个 HashMap 的性能影响较大,尤其是在数据量较大时。扩容操作需要重新计算所有元素的位置,可能导致短暂的性能下降
- 红黑树转换:对单个桶的性能影响较大,但不会影响整个 HashMap 的性能。它主要用于优化冲突较多的链表,提升查询效率
4.SpringBoot的启动原理
1. 博主回答
核心注解:@SpringBootApplication
-
自动配置:@EnableAutoConfiguration 会根据类路径中的依赖自动配置 Spring 应用。Spring Boot 提供了大量的自动配置类(如 DataSourceAutoConfiguration、WebMvcAutoConfiguration 等),这些配置类会根据条件(如依赖存在、Bean 未定义等)自动生效
- 启动依赖组件的时候,组件中必须包含 @Configuration 的配置类,在这个配置类里面声明为 @Bean 注解,就将方法的返回值或者属性值 注入 IoC 容器中
- 如果是使用第三方 jar 包,Spring Boot 采用 SPI 机制,只需要在/META-INF/目录下增加spring.factories 配置文件。然后,Spring Boot 会根据约定规则,自动使用 SpringFactoriesLoader来加载配置文件中的内容
- Spring 获取到第三方 jar 中的配置以后,会使用调用 ImportSelector 接口来完成动态加载
-
组件扫描:@ComponentScan 会扫描指定包路径下的注解,并将这些类注册为 Spring 的 Bean。默认情况下,它会扫描 @SpringBootApplication 所在包及其子包
-
配置类:@SpringBootConfiguration 标记当前类为 Spring Boot 的主配置类,它本身是一个 @Configuration 注解
2. 面试官问的一些细节
实际上面试官想听到的是3次过滤,而不是上面加载spring.factories文件
查看源码:
- 第一次过滤:基于 @Conditional 注解自动配置类通常带有 @Conditional 注解(如 @ConditionalOnClass、@ConditionalOnMissingBean 等),Spring Boot 会根据这些条件注解决定是否加载某个配置类
- 第二次过滤:LinkedHashSet
- 第三次过滤:@SpringBootApplication(exclude = {})