概述
-
ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类
-
该类封装了一个动态可在分配的Object[]数组,每个类对象都有一个capacity表示他们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。
-
ArrayList的用法和Vector类似,但Vector是一个较老的集合,具有很多缺点,不建议使用,区别在于ArrayList是线程不安全的,而Vector则是线程安全的。
-
ArrayList和Collection的关系图:
ArrayList的数据结构
底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。
ArrayList源码分析
继承结构和层次关系
从图中可以得知ArrayList先继承了AbstractList抽象类和实现了List接口。但是AbstractList它也实现了List接口,那为什么ArrayList不直接实现List接口呢?
这里是有一个思想,接口中全都是抽象的方法,而抽象类中可以有抽象方法,还可以有具体的实现方法,正是利用了这一点,让AbstractList实现接口中一些通用的方法,而具体的类,如ArrayList就继承这个AbstractList类,拿到一些通用的方法,然后自己在实现一些自己特有的方法,这样一来,让代码更简洁,就继承结构最底层的类中通用的方法都抽取出来,先一起实现了,减少重复代码。所以一般看到一个类上面还有一个抽象类,应该就是这个作用。
类中的属性
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 版本号
private static final long serialVersionUID = 8683452581122892189L;
// 缺省容量
// 不是在我们调用无参构造创建list的时候它的底层数组就已经设置好长度了 而是当我们add第一个元素的 这个长度才来初始化
private static final int DEFAULT_CAPACITY = 10;
// 空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 缺省空对象数组
// 用于默认大小的空实例的共享空数组实例。
// 我们将其与 EMPTY_ELEMENTDATA 区分开来,以了解添加第一个元素时要膨胀多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素数组
// ArrayList 的元素存储在其中的数组缓冲区。
// ArrayList 的容量就是这个数组缓冲区的长度。任何带有
// elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList
// 添加第一个元素时将扩展为 DEFAULT_CAPACITY。
transient Object[] elementData;
// 实际元素大小,默认为0
private int size;
// 最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
复制代码
构造方法
ArrayList有三个构造方法
1)无参构造方法
/**
* Constructs an empty list with an initial capacity of ten. 这里就说明了默认会给10的大小,所以说一开始arrayList的容量是10.
*/
//ArrayList中储存数据的其实就是一个数组,这个数组就是elementData,在123行定义的 private transient Object[] elementData;
public ArrayList() {
super(); //调用父类中的无参构造方法,父类中的是个空的构造方法
this.elementData = EMPTY_ELEMENTDATA;//EMPTY_ELEMENTDATA:是个空的Object[], 将elementData初始化,elementData也是个Object[]类型。空的Object[]会给默认大小10,等会会解释什么时候赋值的。
}
复制代码
2)有参构造一
/**
* 构造一个具有指定初始容量的空列表
*
* @param initialCapacity 列表的初始容量
* @throws 如果指定的初始容量为负数,则抛出 IllegalArgumentException
*/
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);
}
}
复制代码
小结:arrayList的构造方法就做一件事情,就是初始化一下储存数据的容器,其实本质上就是一个数组,在其中就叫elementData。
核心方法:
1、add方法
1)boolean add(E);//默认直接在末尾添加元素
/**
* 把元素添加到集合的末尾
*/
public boolean add(E e) {
//确定数组容量是否足够,size是数据个数,所以+1.
ensureCapacityInternal(size + 1);
//在数组中正确的位置上添加元素e,并size++
elementData[size++] = e;
return true;
}
复制代码
ensureCapacityInternal(xxx); 确定内部容量的方法
//用于确定数组容量
private void ensureCapacityInternal(int minCapacity) {
//判断初始化elementdata是否为一个空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果是,相当于是空数组没有长度,则获取“默认的容量”和“传入参数”两者之间的最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确认实际的容量,判断是否需要进行扩容操作
ensureExplicitCapacity(minCapacity);
}
复制代码
ensureExplicitCapacity(xxx);
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
//记录修改次数
modCount++;
//如果最小大小 减 数组长度 大于0 -> 进行数组扩容
if (minCapacity - elementData.length > 0)
//实际进入扩容机制
grow(minCapacity);
}
复制代码
grow(xxx); arrayList核心的方法,能扩展数组大小的真正秘密。
/**
* ArrayList扩容的核心方法。
*/
private void grow(int minCapacity) {
// oldCapacity为旧容量,newCapacity为新容量
//将扩充前的elementData大小给oldCapacity
int oldCapacity = elementData.length;
//将oldCapacity 右移一位,其效果相当于oldCapacity/2
//newCapacity就是1.5倍的oldCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
//检查新容量是否大于最小需要容量,若小于最小需要容量,那么就把最小需要容量当作数组的新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
/**
* 检查新容量是否超出了ArrayList所定义的最大容量,
* 若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
* 如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
*/
if (newCapacity - MAX_ARRAY_SIZE > 0)
//比较minCapacity和 MAX_ARRAY_SIZE
newCapacity = hugeCapacity(minCapacity);
//新的容量大小已经确定好了,就copy数组,改变容量大小
elementData = Arrays.copyOf(elementData, newCapacity);
}
复制代码
hugeCapacity();
//这个就是上面用到的方法,很简单,就是用来赋最大值。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果minCapacity都大于MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,反之将MAX_ARRAY_SIZE返回
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
复制代码
2)void add(int,E);在特定位置添加元素,也就是插入元素
/**
* 在此列表中的指定位置插入指定的元素。
*先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
*再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
复制代码
rangeCheckForAdd(index)
private void rangeCheckForAdd(int index) {
if (index > size || index < 0) //插入的位置肯定不能大于size 和小于0
//如果是,就报这个越界异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
复制代码
总结:
正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。
当我们调用add方法时,实际上的函数调用如下:
说明:程序调用add,实际上还会进行一系列调用,可能会调用到grow,grow 会调用hugeCapacity。
remove()方法
其实这几个删除方法都是类似的。我们选择几个讲,其中fastRemove(int)方法是private的,是提供给remove(Object)这个方法用的。
remove(int) 删除指定位置上的元素
public E remove(int index) {
rangeCheck(index);//检查index的合理性
modCount++;//增加修改次数
E oldValue = elementData(index);//通过索引直接找到该元素
int numMoved = size - index - 1;//计算要移动的位数。
if (numMoved > 0)
//这个方法也已经解释过了,就是用来移动元素的。
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将--size上的位置赋值为null,让gc(垃圾回收机制)更快的回收它。
elementData[--size] = null; // clear to let GC do its work
//返回删除的元素。
return oldValue;
}
复制代码
remove(Object)
这个方法可以看出来,arrayList是可以存放null值得。
//通过元素来删除该元素,就依次遍历,如果有这个元素,就将该元素的索引传给fastRemobe(index),使用这个方法来删除该元素,
//fastRemove(index)方法的内部跟remove(index)的实现几乎一样,这里最主要是知道arrayList可以存储null值
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
复制代码
clear()
将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉,所以叫clear
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
复制代码
总结:
remove函数用户移除指定下标的元素,此时会把指定下标到数组末尾的元素向前移动一个单位,并且会把数组最后一个元素设置为null,这样是为了方便之后将整个数组不被使用时,会被GC,可以作为小的技巧使用。
set()方法
设定指定下标索引的元素值
public E set(int index, E element) {
// 检验索引是否合法
rangeCheck(index);
// 旧值
E oldValue = elementData(index);
// 赋新值
elementData[index] = element;
// 返回旧值
return oldValue;
}
复制代码
indexOf()方法
从头开始查找与指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存放null元素的。与此函数对应的lastIndexOf,表示从尾部开始查找。
// 从首开始查找数组里面是否存在指定元素
public int indexOf(Object o) {
if (o == null) { // 查找的元素为空
for (int i = 0; i < size; i++) // 遍历数组,找到第一个为空的元素,返回下标
if (elementData[i]==null)
return i;
} else { // 查找的元素不为空
for (int i = 0; i < size; i++) // 遍历数组,找到第一个和指定元素相等的元素,返回下标
if (o.equals(elementData[i]))
return i;
}
// 没有找到,返回空
return -1;
}
复制代码
get()方法
public E get(int index) {
// 检验索引是否合法
rangeCheck(index);
return elementData(index);
}
复制代码
get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0),值得注意的是,在get函数中存在element函数,element函数用于返回具体的元素,具体函数如下:
E elementData(int index) {
return (E) elementData[index];
}
复制代码
返回的值都经过了向下转型(Object -> E),这些是对我们应用程序屏蔽的小细节。
总结
- ArrayList可以存放null
- ArrayList本质上是一个elementData数组
- ArrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是grow()方法
- arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全删除集合中的元素。
- arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
- arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。