一:基本介绍
ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
ArrayList继承于AbstractList类,实现了List接口;他是一个数组队列,提供了相关的添加、删除、遍历的功能。
ArrayList实现了RandomAccess接口,说明其提供了随机访问的功能;RandomAccess接口是一个标记接口,用以标记实现的List集合具备快速随机访问的能力。所有的List实现都支持随机访问的,只是基于基本结构的不同,实现的速度不同罢了,这里的快速随机访问,那么就不是所有List集合都支持了。
ArrayList基于数组实现,数组带下标,可以实现常量级的随机访问,复杂度O(1).
LinkedList基于链表实现,随机访问需要依靠遍历实现,复杂度为O(n)
ArrayList实现了Serializable接口,这意味着ArrayList 支持序列化,能通过序列化去传输。
ArrayList实现了Cloneable接口,能被克隆。
其于Vector不同的是Vector是线程安全的,因此建议在单线程中才使用ArrayList,多线程中使用Vector。
结构图如下:
二.源码解析
ArrayList源码如下:
1.基本属性:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始化容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享的空对象
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
*一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值;区分上面的EMPTY_ELEMENTDATA,这个第一次添加add时会膨胀(扩容长度为10)
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 保存添加到ArrayList中的元素。
* ArrayList的容量就是该数组的长度。
* 该值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,当第一次添加元素进入
* ArrayList中时,数组将扩容值DEFAULT_CAPACITY。
* 被标记为transient,在对象被序列化的时候不会被序列化。
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 数组长度
* @serial
*/
private int size;
/**
* 最大数组容量
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2.构造函数
2.1:无参构造
/**
* 构造一个初始容量为10的空列表
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
说明:构造一个容量为10的空列表,在创建时不传入参数,使用当前的无参构造去创建,前面的属性得知DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的Object[],将elementData 初始化,elementData 也是个Object[], 属性介绍上表示空的elementData 会给初始容量为10,那是因为第一次add()时给了初始化容量10;
2.2:带int的有参构造
/**
*构造具有指定初始容量的空列表
*
* @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);
}
}
说明:当initialCapacity大于等于0时,那么initialCapacity为ArrayList的初始化长度,当initialCapacity小于0时,报错:参数不合法;
2.3:带Collection对象的构造函数
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么就需要使用ArrayList中的方法去改造一下。
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
说明:
1.构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。第二个有参构造方法构造时赋的值是它的父类Collection对象。
2.将collection对象转换成数组,然后将数组的地址的赋给elementData。
3.如果数组的实际大小等于0(c中没有元素),将空数组EMPTY_ELEMENTDATA赋值给elementData
4.如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。
3.方法
3.1:add方法
add方法分为两个,一个参数和两个参数的
3.1.2:add(E e)
添加元素方法入口:
/**
* Appends the specified element to the end of this list.
* 将指定的元素追加到此列表的末尾
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 确认list容量,如果不够,容量加1。
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//数组容量检查,不够时则进行扩容,只供类内部使用
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//确保添加的元素有地方存储,当第一次添加元素的时候this.size+1 的值是1,所以第一次添加的时候会将当前elementData数组的长度变为10:保证添加的元素有地方存储。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//容量检查,将修改次数(modCount)自增1,判断是否需要扩充数组长度,判断条件就是用当前所需的数组最小长度与数组的长度对比,如果大于0,则增长数组长度。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 最小容量>数组缓冲区当前长度
if (minCapacity - elementData.length > 0)
// 增加数组长度
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
说明:
1.添加单个元素时,会先进行容量检查。此时将size+1,保证加入的对象有空间可以存储。ensureCapacityInternal(int minCapacity)此时的minCapacity为size+1;
2.再进行计算加入的数据要多少容量能容纳,ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));调用这个方法,其中calculateCapacity(elementData, minCapacity)计算容量,如果elementData为空,那么此时返回默认容量大小为10;否则则返回minCapacity。
3.modCount++;修改次数加1;如果最小的容量大于了当前list的容量大小,将进行扩容;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
调用grow进行扩容。
扩容机制如下:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 原基础上扩大1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// //如果扩容后的容量大于临界值,则进行大容量分配
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//如果minCapacity<0,抛出异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果想要的容量大于MAX_ARRAY_SIZE则分配Integer.MAX_VALUE:2147483647,否则分配MAX_ARRAY_SIZE:2147483639。
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
扩容,保证ArrayList至少能存储minCapacity个元素 ,第一次扩容,逻辑为newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基础的1.5倍。 第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为minCapacity。
3.1.3:add(int index, E element)
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*在此列表的指定位置插入指定元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(在其索引上加1)
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
// 范围检查;如果index的位置超出了数组的大小。或者小于0,抛异常IndexOutOfBoundsException
rangeCheckForAdd(index);
// 检查是否需要扩容,跟添加一个元素类似;
ensureCapacityInternal(size + 1); // Increments modCount!!
//确保有足够的容量之后,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 插入赋值
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3.1.4:addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
说明:主要涉及数组的容量扩容,新的list的大小为原数组大小+插入的集合的空间大小。
3.1.5:addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
// 检查数组大小
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
// 数组扩容
ensureCapacityInternal(size + numNew); // Increments modCount
// 移动的个数
int numMoved = size - index;
if (numMoved > 0)
// 从传入的下标位置,原数组在index位置开始往后移
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 插入collection
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
3.2:get,set方法
3.2.1:get(int index)
public E get(int index) {
// 检查数组下标,大于 等于了数组大小,抛异常IndexOutOfBoundsException
rangeCheck(index);
// 返回当前下标位置的元素
return elementData(index);
}
3.2.2:set(int index, E element)
确保set的位置小于当前数组的长度(size)并且大于0,获取指定位置(index)元素,然后放到oldValue存放,将需要设置的元素放到指定的位置(index)上,然后将原来位置上的元素oldValue返回给用户。
public E set(int index, E element) {
// 检查数组下标,大于 等于了数组大小,抛异常IndexOutOfBoundsException;确保有容量;
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
3.3:remove方法
3.3.1:remove(int index)
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*删除位于列表中指定位置的元素。将所有后续元素左移(从其下标减去1)。
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
// 检查给定的索引是否在范围内,不然则报错
rangeCheck(index);
// 自增修改次数
modCount++;
// 将要删除的元素给oldValue
E oldValue = elementData(index);
// 得到删除后,需要往左移的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 拷贝覆盖
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 清理内存空间,给GC回收
elementData[--size] = null; // clear to let GC do its work
// 返回旧值
return oldValue;
}
说明:删除时:1.先检查数组下标;
2.将删除的元素赋给oldValue;
3.找出要向左移的元素个数
4.进行拷贝覆盖
5.数组最后一位置空,方便GC回收
6.返回旧值
3.3.2:remove(Object o)
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;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
说明:根据对象来删除元素,分为两种情况:
1.需要删除的对象为null的情况
1.1:遍历判断对象是否为空,为空则调用fastRemove(index)方法进行删除;
2.需要删除的对象不为null的情况,遍历判断得到当前数组中与删除的对象相同的值,调用fastRemove(index);进行删除。
3.3.3:removeRange(int fromIndex, int toIndex)
// 删除下标fromIndex(包含一起删了)到toIndex(保留不删)的数据
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
// 得到要左移的元素个数
int numMoved = size - toIndex;
// 拷贝覆盖
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
// 从什么位置开始释放空间
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
3.3.4:clear()方法
//从列表中删除所有元素。此调用返回后,列表将为空
public void clear() {
modCount++;
// 遍历置空,给GC回收
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
3.4:其他方法:
3.4.1:indexOf(Object o)
返回o第一次出现的下标为,如果没得则返回-1;同时得区分传入的对象是否为空的情况,分开判断。
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
* 返回列表中指定元素第一次出现的索引,如果列表中不包含该元素,则返回-1。更正式地,返回最低索引,如果没有这样的索引,则返回-1。
*/
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;
}
3.4.2:contains(Object o)
直接调用indexOf(o)方法,得到当前对象出现的索引位置,再与0判断大小。返回boolean 类型
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
三、总结
1)arrayList可以存放null。
2)arrayList本质上就是一个elementData数组。
3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
4)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,LinkedList较快 。
5)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。