在 Java 中,ArrayList 内部是通过一个数组来存储元素的,是一个数组结构的存储容器。当向一个 ArrayList 中添加元素时,如果当前数组已经满了,就需要扩容。

集合的继承关系图

一、面试回答

( ArrayList 的扩容机制原理)

面试官好,ArrayList 是一个数组结构的存储容器,默认情况下,设置数组长度是 10. 当然我们也可以在构建 ArrayList 对象的时候自己指定初始长度。 随着在程序里面不断的往 ArrayList 中添加数据,当添加的数据达到 10 个的时候, ArrayList 就没有多余容量可以存储后续的数据。 这个时候 ArrayList 会自动触发扩容。 扩容的具体流程很简单, 1. 首先,创建一个新的数组,这个新数组的长度是原来数组长度的 1.5 倍。 2. 然后使用 Arrays.copyOf 方法把老数组里面的数据拷贝到新的数组里面。 扩容完成后再把当前要添加的元素加入到新的数组里面,从而完成动态扩容的过程。 以上就是我对这个我对这个问题的理解!

或者不直接问:ArrayList 扩容是在第10个元素还是第11个元素触发的 ” />import java.util.ArrayList;@SuppressWarnings({“all”})public class ArrayListSource {public static void main(String[] args) {//解读源码//注意,注意,注意,Idea 默认情况下,Debug 显示的数据是简化后的,如果希望看到完整的数据//需要做设置. //使用无参构造器创建 ArrayList 对象ArrayList list = new ArrayList();//ArrayList list = new ArrayList(8);//使用 for 给 list 集合添加 1-10 数据for (int i = 1; i <= 10; i++) {list.add(i);}//使用 for 给 list 集合添加 11-15 数据for (int i = 11; i <= 15; i++) {list.add(i);}list.add(100);list.add(200);list.add(null);}}

F7单步调试进行下一步,遇到方法会进入方法内,同一行有多个方法时可以用左右键选择;
F8单步调试,进行下一步,不会进入方法内;
Alt+Shift+F7强制进入方法内;
Shift+F8直接跳出方法;
F9跳到下一个断点或者直接执行完程序

首先进入ArrayList的构造器,看到 elementData第一次初始化的时候就是一个空数组

接下来进入到for循环,第一次进去会把int给类型转化,进行一个装箱操作。

然后在添加的时候 ,先执行了判断这个要添加的这个e的大小是否达到要求,满足了再将e放入

第一次返回的一定是 10 (是规定好的了)

拿到 minCapacity 然后再进入到 ensureExplicitCapacity 其中modCount 表示被修改的次数(这里主要防止有多个线程同时去修改,如果有,则会抛出异常)

只有当 elementData的大小小于10的时候就调用grow方法进行扩容。

先把传进来的数组大小赋值给一个变量 oldCapacity ,然后按照原先数组的1.5倍进行扩容(右移一位,相当于除以2)而 Arrays.copyOf其实就是数组的复制。

即得到了10个 elementData的空元素

ArrayList 的扩容机制是在添加元素时判断当前数组大小是否已经满了,如果已经满了,则创建一个新的更大的数组,并将原来的元素全部复制到新的数组中。具体的扩容规则如下:

  1. 当添加元素后,size 大小已经等于或超过了数组的容量 capacity 时,就会触发扩容操作。
  2. 扩容的大小默认情况下为原数组大小的一半。比如原数组大小为 10,那么扩容后的数组大小为 15。
  3. 如果扩容后的大小还是不够,那么就以添加元素的数量作为扩容大小,即新数组的大小为 oldCapacity + (oldCapacity >> 1) + 1。

需要注意的是,ArrayList 中的数组无法动态地调整大小,因此每次扩容都需要创建新的数组和复制元素,这可能会带来一些性能损失。为了避免频繁扩容,我们可以在使用 ArrayList 时尽量预估元素数量,初始化时指定一个合适的初始容量。

在实际使用中,我们可以通过指定初始容量和适当的预估来优化扩容操作,以提高性能。