相信大家学Java基础的时候,多多少少肯定听过一些关于ArrayList的扩容机制分析。基本上都是这么讲的:
- 当new ArrayList<>()后,底层会创建一个长度为0的数组
- 当调用add方法添加一个元素时,ArrayList底层会创建一个长度为10的数组,然后将新增元素添加到数组末尾
- 当添加元素后,元素个数超过10之后,就会触发ArrayList的自动扩容机制,默认扩容为原数组的1.5倍
这里这么讲其实是不严谨的,因为ArrayList中还有一个成员方法叫addAll,传递的参数为一个Collection集合,当达到10个上限后,我一次性添加100个元素,这个时候扩容1.5倍是不够,这个时候就需要按需扩容,我们来看下ArrayList的底层源码:
1. ① new ArrayList<>() ② new ArrayList<>(int size) ③ new ArrayList<>(Collection<? extends E> c)
1.1 new ArrayList<>() 空参构造
当我们new一个ArrayList的时候,如果我们不进行参数传递,其实这个时候ArrayList底层默认创建一个Object类型的数组:
其中的常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA就是默认的Object[]
1.2 new ArrayList<>(int size) 带数组长度的构造
如果new ArrayList的时候传递了初始数组大小,那么ArrayList底层将会帮你创建一个你传递长度的数组,但是这个时候也需要进行长度参数判断:
- 大于0,则按照你传递的参数进行创建
- 等于0,按照空参构造的方式进行创建空数组
- 小于0,报错
1.3 new ArrayList(Collection<? extends E> c) 带集合参数的构造
- 首先将传入的集合转换为数组,然后判断数组的长度是否等于0
-
- 等于0,则还是按照空参构造的方式进行创建数组
-
- 不等于0,则判断当前传入的集合是否是ArrayList集合,
-
-
- 如果是,则直接将转换后的a数组赋值给当前ArrayList的数组
-
-
-
- 如果不是,则使用Arrays中的copyOf方法将该数组进行二次复制,
-
-
这里就会有人问了,前面我已经进行toArray转换了,为什么这里还需要分情况判是否是ArrayList集合,再去判断时候需要二次复制?作者也有这样的疑惑,网上查阅了相关资料后,分享给大家:如果 c 是 ArrayList 类型,那么它的底层实现也是一个数组,因此可以直接使用 elementData = a; 进行赋值。这样做可以避免额外的数组复制操作,提高效率。如果 c 不是 ArrayList 类型,那么使用 Arrays.copyOf(a, size, Object[].class); 创建一个新的数组。
这里使用 Arrays.copyOf 的原因在于安全性和隔离性。如果不使用 copyOf,而是直接将原数组赋值给 elementData,那么原数组和 ArrayList 的 elementData 就会共享同一块内存区域。这意味着,如果在其他地方修改了原数组的内容,那么 ArrayList 中的数据也会被修改,这可能会导致一些不可预见的错误。通过使用 Arrays.copyOf,我们可以创建一个新的数组,这样 ArrayList 的 elementData 就和原数组是完全独立的,更加安全。
这种做法也符合Java中的封装原则,即每个对象都应该对自己的内部状态负责,不应该被外部对象直接修改。
2. add(E e) 添加元素