ArrayList集合的底层实现原理以及其在面试中的常见问题
java.util.ArrayList集合,其数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。
本文对于ArrayList集合的学习主要关心下面几个问题,也是在面试中经常会被提问到的问题:
1. ArrayList底层是什么?
2. ArrayList如何实现扩容?
3. ArrayList删除怎么实现?
首先,我们先来创建一个ArrayList集合:
public class Demo01 { public static void main(String[] args) { ArrayList<Integer> integers = new ArrayList<>(); integers.add(1); integers.add(2); integers.add(3); integers.add(4); integers.add(5); integers.add(6); integers.add(7); integers.add(8); System.out.println(integers); } }
集合运行结果如下:
1. ArrayList底层是什么?
在IntelliJ IDEA开发软件中,使用Ctrl键+鼠标左键,点击进入ArrayList集合的无参构造方法:
可见在底层中创建了elementData这个ArrayList集合的对象:
再Ctrl键+鼠标左键点击查看elementData是一个什么类型的元素:
由此可知,elementData的类型是一个Object[ ](任意类型数组)的元素,所以ArrayList底层其实是创建了一个数组。
2. ArrayList如何实现扩容?
我们已知ArrayList的底层通过创建数组来保存数据,那么由于一个已经被创建的数组其长度是不能变化的,那么ArrayList集合为什么能够继续添加新数据呢?为什么其集合长度能不断增加呢?为解决这个问题,我们来Debug运行最上面那段创建出一个ArrayList集合的代码:
- 底层数组被创建出来,但数组初始化长度为0,仍未添加数据:
- s的值是0,数组的长度elementData.length也是0,因此相等时会执行grow()方法,也就是ArrayList的扩容方法:
- 下面我们重点来关注grow()方法都做了哪些操作实现了集合的扩容,Arrays.copyOf()方法是数组工具类Arrays中一个复制已有数组中内容到一个新数组中的静态方法(其底层实现是通过System.arrayCopy()方法),这里的elementData是之前创建的旧数组,newCapacity(minCapacity) 是新创建出来的数组的长度。到这里我们也明白了,ArrayList集合的扩容其实就是将添加的内容连同旧数组的内容复制到一个新数组之中:
- 那么新数组的长度是如何确定的呢?首先是第一次扩容,第一次扩容是因为底层数组在被创建时的长度在初始化时就为0,因此在添加第一个数据时必须要创建出一个指定长度的新数组。
- newCapacity的值在一次创建时是0,minCapacity的值是1,因此newCapacity - minCapacity <= 0,进入下一条判断语句:elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,判断elementData是否是初始数组,如果是则执行Math.max(DEFAULT_CAPACITY, minCapacity)方法,意思是返回参数中的较大值作为新数组的长度:
- 进一步来看Math.max()方法做了些什么,DEFAULT_CAPACITY的参数值即为a=10,minCapacity的值为1,因此返回的是两者中的较大值为10,因此可见,在第一次创建ArrayList集合并添加数据时,为向初始化值为0的底层数组中添加元素,便进行了第一次集合的扩容,即创建了一个长度为10的数组:
- 此后可以继续向集合中添加数据,当集合长度增加到10时,便要进行第二次扩容,下面再来看当数组的长度为10时,触发的第二次扩容:
发现此时数组的长度已经为10,判断触发grow()方法,即扩容方法
- MAX_ARRAY_SIZE是指Integer的最大长度,即2147483639,判断新数组长度是否超过这个长度,如果没有超过则第二次扩容的长度即为newCapacity=15,如果超过则会执行对数组内的元素进行裁减(hugeCapacity(minCapacity)方法),防止超过此数组最大长度:
第二次扩容完毕,底层新数组的长度变为了15(此处可见扩容后新数组的大小约为原来长度的1.5倍):
当ArrayList集合所容纳的数据其集合长度再次到达临界点,即此时的底层数组长度时,将继续触发扩容,原理同第二次扩容,此处不再赘述。
3. ArrayList删除怎么实现?
ArrayList的删除方法是 public E remove(int index) {…}方法, int index为要删除元素在集合中的索引,下面来看删除方法的调用效果:
integers.remove(2);
下图表示ArrayList删除的实现原理,即将指定索引位置后的元素向前移动一个位置,若索引后面需要向前移动的元素较多则删除速度较慢:
- hashmap底层实现原理以及常见的面试问题
- HashMap底层实现原理及面试问题
- Java集合之ArrayList和LinkedList的实现原理以及Iterator详解
- Java集合 ArrayList底层实现原理
- Java集合之ArrayList和LinkedList的实现原理以及Iterator详解
- IOS-SDWebImage 底层实现原理以及面试题相关问题的学习链接
- Java集合,ArrayList底层实现和原理
- Java面试绕不开的问题: Java中HashMap底层实现原理(JDK1.8)源码分析
- 菱形继承引发的问题和解决方案,以及底层实现的原理.
- Java集合系列之ArrayList底层实现原理
- Java集合---ConcurrentHashMap原理分析(面试问题:ConcurrentHashMap实现原理是怎么样的)
- 集合List和ArrayList等实现类的底层原理分析
- HashMap底层实现原理及面试问题
- HashMap底层实现原理及面试问题
- HashMap深入理解详细分析原理以及常见面试问题
- 解决WinForm中ComboBox控件的“设置"DataSourse”属性后无法修改项集合”以及两个不相关联的ComboxBox控件实现数据列表显示不可实现的问题
- Hashtable,HashMap,ConcurrentHashMap 底层实现原理与线程安全问题
- 最难面试的公司以及常见面试问题
- 深入Java集合学习系列:ArrayList的实现原理
- ArrayList集合的实现原理