〇. 媒介
本文将体系的先容Java口试高频考点:ArrayList。内容包括但不限于
- ArrayList 源码分析
- ArrayList 常问口试题
注意:若未经特殊说明,全部的内容将基于JDK1.8进行分析解释。
一. 源码分析(存储逻辑、扩容机制)
1.首先看一下 ArrayList 中的 核心成员变量
- // 1.默认初始容量
- private static final int DEFAULT_CAPACITY = 10;
- // 2.空数组——带参构造【参数为0】 或 【空集合】转换
- private static final Object[] EMPTY_ELEMENTDATA = {};
- // 3.空数组——仅【默认构造】使用
- private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
- // 4.数组缓冲区(存储具体的数组元素)
- transient Object[] elementData;
- // 5.元素数量
- private int size;
复制代码 2.下面是 ArrayList 的三个 构造方法
- // 1.带参构造(指定容量)
- public ArrayList(int initialCapacity) {
- if (initialCapacity > 0) { // 创建定长数组
- this.elementData = new Object[initialCapacity];
- } else if (initialCapacity == 0) { // 赋空数组
- this.elementData = EMPTY_ELEMENTDATA;
- } else { // 抛异常
- throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
- }
- }
- // 2.默认构造
- public ArrayList() {
- // 赋空数组
- this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
- }
- // 3.单列集合Collection转ArrayList
- public ArrayList(Collection<? extends E> c) {
- // 集合转数组,存入ArrayList(不管类型)
- elementData = c.toArray();
- if ((size = elementData.length) != 0) { // 长度>0
- if (elementData.getClass() != Object[].class)
- // 发现Collection和ArrayList定义的类型不同,做类型转换
- elementData = Arrays.copyOf(elementData, size, Object[].class);
- } else { // 赋空数组
- this.elementData = EMPTY_ELEMENTDATA;
- }
- }
复制代码 3.⭐ArrayList的 存储逻辑 以及 扩容机制
我们先梳理一下,使用不同的构造方法创建ArrayList对象后的情况:
- 默认构造:数组容量:0,元素数量size:0;
- 带参构造:数组容量:参数,元素数量size:0;
- 聚集转换:数组容量 == 元素数量,继承参数聚集(0~n)。
接下来先容:如果我们 向ArrayList中添加元素,会发生什么。
【STEP 01】准备变量 numNew:待添加元素的长度。
① add方法:默以为1;
② addAll方法:待添加聚集的长度。
【STEP 02】准备变量 mincapacity:添加元素之后,希望数组达到的长度。
① 常规情况下,mincapacity = 现有元素数量size + numNew;
② 唯一特殊情况:ArrayList使用 默认构造创建,并且 首次添加元素,mincapacity = max( numNew , 10 )。
【STEP 03】比较 mincapacity 与 数组容量elementData.length
① mincapacity > 数组容量:开始扩容;
② mincapacity <= 数组容量:无需扩容。
【STEP 04】扩容机制
首先,实验 把 数组容量 扩充至原来的 1.5倍;
然后,再次 比较 mincapacity 与 数组容量 的大小;
① mincapacity <= 数组容量:数组容量 = 原容量的1.5倍;
② mincapacity > 数组容量:数组容量 = mincapacity。
第①种情况常见于mincapacity不太大的情况,比方:聚集使用add添加元素…
第②中情况常见于mincapacity较大的情况,比方:step02中的特殊情况,或使用addAll添加了一个超长聚集…
【STEP 05】数组容量达标,向数组中添加元素即可
比方:elementData[size++] = e;
至此,ArrayList 源码中最核心的部分 —— 元素添加 与 扩容机制 就已经完成了;
请熟练掌握这部分源码,后续各种口试题的理解会更加轻松,印象会更加深刻!
二. 高频口试题
1. ArrayList底层的实现原理?
本题的答案就是上述源码分析的内容,再来总结一下:
① ArrayList 底层是用 动态数组 实现的;
② 以 默认构造 方法为例,数组初始容量为 0,首次 添加数据后扩容至 10;
③ 后续每次扩容都是原来容量的 1.5倍,每次扩容都必要 拷贝数组;
④ 在我们 添加数据 的时候有固定的逻辑:
- 首先,获取 add / addAll 添加的 新增数据的长度 newNum;
- 其次,计算数组 目的容量mincapacity=size+newNum,如果数组长度小于该目的,则必要调用 grow方法 为数组扩容;
- 扩容时,首先实验扩容到原来的 1.5倍,如果还不能满意目的容量,则直接扩容到 目的容量;
- 最后,向数组内 添加 新增数据,返回添加成功的 布尔值。
2. new ArrayList(10) 扩容了频频?
答:并没有扩容!
ArrayList聚集的扩容机制只会在 添加数据时触发。new ArrayList(10) 只是定义了一个 初始容量为10的聚集对象,并没有触发扩容。
3. 如何实现 数组 与 List 之间的转换?
请注意:为了确保类型一致,数组的类型应包管为【包装类】。
(1)数组 转 List
方法①:Collections.addAll(list, array)【最高效】
方法②:循环遍历array并依次list.add添加【最朴素】
方法③:Arrays.asList(array)【查改✔ 增删❌】
方法④:new ArrayList<>(Arrays.asList(array))【改良方法③】
此中应注意,方法③:Arrays.asList(array) 得到的是 定长List聚集,全部的后续操纵 不得改变聚集长度。
并且该方法还有 “数据同步修改” 的题目,将在“两个追问”中提及。
- Integer[] arr = {1, 2, 3, 4, 5};
- List<Integer> list1 = Arrays.asList(arr);
- List<Integer> list2 = new ArrayList<>(list1);
- // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
- System.out.println(list1 + " " + list2);
- arr[0] = 0; // ⭐修改数据(后续解释)
- // [0, 2, 3, 4, 5] [1, 2, 3, 4, 5]
- System.out.println(list1 + " " + list2);
- list1.add(6); // 运行报错:UnsupportedOperationException
- list2.add(6);
复制代码 (2)List 转 数组
使用 List 的 toArray方法 即可,注意 无参 / 带参 的区别!
- // 无参返回Object数组
- Object[] array1 = list2.toArray();
- // 带参返回原类型数组
- Integer[] array2 = list2.toArray(new Integer[0]);
- // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
- System.out.println(Arrays.toString(array1) + " " + Arrays.toString(array2));
复制代码 (3)“两个追问”
① 题目一:对于 Arrays.asList() 获取的聚集,当数组变化时,List受影响吗?
回复:受影响(从上面的代码示例可以清晰看出)!
解释:Arrays.asList()只是调用了一个名叫 ArrayList的内部类 而已,该类对传入的数组进行了 包装,但是数据元素依然指向 同一块内存地址。过程中并没有一个独立的ArrayList聚集产生。
② 题目二:对于 List的toArray() 获取的数组,当聚集变化时,数组受影响吗?
回复:不影响!
解释:List的toArray() 的底层逻辑是 数组拷贝,将List聚集中的数组复制一份并返回,前后是两个完全不同的数组,故两者互不影响。
4. ArrayList 和 LinkedList 的区别?
对于这两个常用的List实现类,我们应该从4个方面去报告它们的区别。
(1)数据布局
ArrayList 的底层数据布局是 动态的数组;
LinkedList 的底层数据布局是 双向链表;
有了这一个底子认知,我们就很轻易理解下面的方面了。
|