【口试资料】Java聚集篇 之 ArrayList

打印 上一主题 下一主题

主题 1894|帖子 1894|积分 5682

〇. 媒介

本文将体系的先容Java口试高频考点:ArrayList。内容包括但不限于


  • ArrayList 源码分析
  • ArrayList 常问口试题
注意:若未经特殊说明,全部的内容将基于JDK1.8进行分析解释。
一. 源码分析(存储逻辑、扩容机制)

1.首先看一下 ArrayList 中的 核心成员变量
  1. // 1.默认初始容量
  2. private static final int DEFAULT_CAPACITY = 10;
  3. // 2.空数组——带参构造【参数为0】 或 【空集合】转换
  4. private static final Object[] EMPTY_ELEMENTDATA = {};
  5. // 3.空数组——仅【默认构造】使用
  6. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  7. // 4.数组缓冲区(存储具体的数组元素)
  8. transient Object[] elementData;
  9. // 5.元素数量
  10. private int size;
复制代码
2.下面是 ArrayList 的三个 构造方法
  1. // 1.带参构造(指定容量)
  2. public ArrayList(int initialCapacity) {
  3.     if (initialCapacity > 0) { // 创建定长数组
  4.         this.elementData = new Object[initialCapacity];
  5.     } else if (initialCapacity == 0) { // 赋空数组
  6.         this.elementData = EMPTY_ELEMENTDATA;
  7.     } else { // 抛异常
  8.         throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
  9.     }
  10. }
  11. // 2.默认构造
  12. public ArrayList() {
  13.     // 赋空数组
  14.     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  15. }
  16. // 3.单列集合Collection转ArrayList
  17. public ArrayList(Collection<? extends E> c) {
  18.     // 集合转数组,存入ArrayList(不管类型)
  19.     elementData = c.toArray();
  20.     if ((size = elementData.length) != 0) { // 长度>0
  21.         if (elementData.getClass() != Object[].class)
  22.             // 发现Collection和ArrayList定义的类型不同,做类型转换
  23.             elementData = Arrays.copyOf(elementData, size, Object[].class);
  24.     } else { // 赋空数组
  25.         this.elementData = EMPTY_ELEMENTDATA;
  26.     }
  27. }
复制代码
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聚集,全部的后续操纵 不得改变聚集长度
并且该方法还有 “数据同步修改” 的题目,将在“两个追问”中提及。
  1. Integer[] arr = {1, 2, 3, 4, 5};
  2. List<Integer> list1 = Arrays.asList(arr);
  3. List<Integer> list2 = new ArrayList<>(list1);
  4. // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
  5. System.out.println(list1 + " " + list2);
  6. arr[0] = 0; // ⭐修改数据(后续解释)
  7. // [0, 2, 3, 4, 5] [1, 2, 3, 4, 5]
  8. System.out.println(list1 + " " + list2);
  9. list1.add(6); // 运行报错:UnsupportedOperationException
  10. list2.add(6);
复制代码
(2)List 转 数组

使用 List 的 toArray方法 即可,注意 无参 / 带参 的区别!
  1. // 无参返回Object数组
  2. Object[] array1 = list2.toArray();
  3. // 带参返回原类型数组
  4. Integer[] array2 = list2.toArray(new Integer[0]);
  5. // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
  6. 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 的底层数据布局是 双向链表
有了这一个底子认知,我们就很轻易理解下面的方面了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天津储鑫盛钢材现货供应商

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表