ArrayList是实现了List接口的动态数组,所谓动态数组就是他的大小是可变的。实现了所有可选列表操作,并允许包括Null在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。默认初始容量是10。默认初始容量为10。随着ArrayList中元素的增加,它的容量也会不断的自动增长。在每次添加元素时,ArrayList都会检查是否需要进行扩容操作,扩容操作带来数据向新数组的重新拷贝,所以如果我们知道具体业务数据量,在构造ArrayList时,可以给ArrayList 指定一个初始容量,这样就会减少扩容时的拷贝问题。当然在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
ArrayList是实现List接口的,底层采用数组实现,所以它的操作基本上都是基于对数组的操作。
/**
* 构造一个具有指定初始容量的空列表。
*/
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);
}
}
/**
* 构造一个初始容量为 10 的空列表
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //不会立刻创建数组,会在第一次add()时才会创建
}
/**
* 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection
* 的迭代器返回它们的顺序排列的。
*/
public ArrayList(Collection extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection extends E> c)、addAll(int index, Collection extends E> c)、set(int index, E element)这个五个方法来实现ArrayList增加。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 验证是否需要扩容
elementData[size++] = e; //往数组最后的位置赋值
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index); //判断索引位置是否正确
ensureCapacityInternal(size + 1); // 验证是否需要扩容
/*
* 对源数组进行复制处理(位移),从index + 1到size-index。
* 主要目的就是空出index位置供数据插入,
* 即向右移动当前位于该位置的元素以及所有后续元素。
*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//给指定下标的位置赋值
elementData[index] = element;
size++;
}
在这个方法中最根本的方法就是System.arraycopy()方法,该方法的根本目的就是将index位置空出来以供新数据插入,这里需要进行数组数据的右移,这是非常麻烦和耗时的,所以一般不建议使用该方式添加元素。如果需要向指定中间位置进行大量插入(中间插入)操作,推荐使用LinkedList。
public boolean addAll(Collection extends E> c) {
Object[] a = c.toArray(); //将集合c 转换成数组
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 扩容(当前集合长度+c集合长度)
//同上,主要是采用该方法把C集合转为数组后的数据进行复制在插入到当前集合的末尾
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection extends E> c) {
rangeCheckForAdd(index); //判断下标位置是否正确
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 扩容(当前集合长度+c集合长度)
int numMoved = size - index;
// 如果位置不是集合尾部,则需要先把数据向右移动指定长度(添加数据的长度)
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 然后再把数据加入到指定下标位置
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
这个方法无非就是使用System.arraycopy()方法将C集合(先准换为数组)里面的数据复制到elementData数组中。这里就稍微介绍下System.arraycopy(),因为下面还将大量用到该方法。
该方法的原型为:public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)。它的根本目的就是进行数组元素的复制。
即从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。将源数组src从srcPos位置开始复制到dest数组中,复制长度为length,数据从dest的destPos位置开始粘贴。
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
ArrayList提供了get(int index)用读取ArrayList中的元素。由于ArrayList是动态数组,所以我们完全可以根据下标来获取ArrayList中的元素,所以他的时间复杂度是O(1);存/取的速度是还是比较快的。但如果是查找,插入和删除元素效率就不会太高,因为他们需要去一个一个遍历出结果来进行对比。所以时间复杂度会为O(n)。
在上面的新增方法的源码中我们发现每个方法中都存在这个方法:ensureCapacity(),该方法就是ArrayList的扩容方法。在前面就提过ArrayList每次新增元素时都会需要进行容量检测判断,若新增元素后元素的个数会超过ArrayList的容量,就会进行扩容操作来满足新增元素的需求。
所以如果当我们清楚知道业务数据量或者需要插入大量元素前,我们可以使用再创建集合时直接指定容量大小,或者通过ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
//minCapacity :所需的最小容量
private void grow(int minCapacity) {
// 集合长度
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //增加原来的1.5倍
// 判断所需要的最小容量大于增加原来的1.5倍的长度,则容量扩大为minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //判断是否会大于虚拟机的最大容量
newCapacity = hugeCapacity(minCapacity);
// 拷贝数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
扩容逻辑为:
- 调用无参ArrayList()时,初始容量是0;调用有参时,会是你给的参数大小;调用集合会是集合的大小
- 当添加元素时就会进行扩容,会扩容一个10个容量的数组,当集合元素到10个时,会进行第二次扩容,第二次扩容会扩容到上一个数组长度的1.5倍(底层逻辑为:15>>1=7+15=22)先右移移位在加上原来的数组长度,
Linkedlist基于链表的动态数组(双向链表):
data:数据域,存储数据
next:指针域,指向下一个结点的存储位置
查询操作
新增和删除时间复杂度
而双向链表,顾名思义,就是支持两个方向:
当需要获取前一个结点时,只需要调用prev就行,调取后一个结点则只需要调用next指针就行
查询:
增删: