ArrayList是平时编码中被使用最多容器之前,与数组有许多相似之处,但是又有很多不同,比如声明时无需声明容器的大小,但是在添加元素时又给人感观是元素的数量没有限制,那么究竟其原理是怎么实现的?让我们一探究竟。
*this class provides methods to manipulate the size of the array that is
* used internally to store the list. (This class is roughly equivalent to
* <tt>Vector</tt>, except that it is unsynchronized.)
此类提供了一些方法来操作内部用于存储列表的数组的大小。(该类大致相当于<tt>vector<tt>,只是它不同步。)
从上面这句话中可以看出ArrayList是数组的一个封装类,内容会提供一些方法用来操作数组,同时注明它相当于vector,但是它不是线程安全的。
认祖归宗:
- 继承了AbstractList<E>抽象类、实现了List<E>接口(List<E>实现了容器类的老祖宗Collection<E>),提供了基本的增删改查等方法;
- 实现了RandomAccess,提供了随机访问的功能,在ArrayList中可以通过下标来快速获取元素对象;
- 实现了Cloneable,支持被克隆;
- 实现了java.io.Serializable,支持序列化。
变量的组成:
/**
*序列化ID
*/
private static final long serialVersionUID = 8683452581122892189L;
/**
* 数组默认长度(这里需要啰嗦几句:ArrayList的默认size为0,
只有在通过无参构造方法创建的对象和指定ArrayList大小为0时,第一调用add方法,才会变成size为10,后面的源码部分可以看到)
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 指定容器大小构造参数初始化实现的空数组(指定数组大小为0)
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 无参构造器初始化实现的空数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 当前数组大小,不参与序列化(transient)
*/
transient Object[] elementData;
/**
*当前数据大小
*/
private int size;
/**
* 临界值,数组大小的最大值
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
下面在来看看ArrayList的三个构造方法
构造方法是初始化一个数组
1、指定容器大小的构造方法,参数为int型,如果参数>0,则参数大小即为数值elementData大小,如果传入参数为0则将空数组EMPTY_ELEMENTDATA赋值给数组elementData,小于0则抛出异常。
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);
}
}
2、无参构造方式,不传入参数则默认为空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3、带Collection对象的构造方法,将对象转化为数组,如果数组大小等于0,直接将空数组EMPTY_ELEMENTDATA赋值给elementData,如果数组大小不为0,执行拷贝动作(这里的拷贝动作是深拷贝还是浅拷贝)赋值给执行拷贝动作。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
下面来介绍下主要方法:
根据对象元素获取元素在容期中的位置,从代码中可以看出,这个过程需要遍历数组,所有最好情况时间复度为O(1),最坏情况为o(n)。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;
}
添加元素:public boolean add(E e),在数组末尾加入元素,主要实现了数组动态扩容,可能会进行一次数据拷贝
//当前数组大小加1和默认长度10对比,如果小于默认长度,则将添加元素赋值给当前数组elementData对
//应下标位置;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果是默认默认数组,数组大小和默认大小取最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//判断是否需要扩容
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//数组修改次数加1
//期望值比实际数组大,则需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);//扩容
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//大小增加为原有大小的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果还是比期望值小,则以期望值大小为最后的size
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//大于边界值,在进行判断
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//执行copy动作,也就是数据搬移动作
elementData = Arrays.copyOf(elementData, newCapacity);
}
//数组的大小的最大值是int的最大值
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
指定位置添加元素:public void add(int index, E element) ,可能会进行一次数据拷贝,会进行数据搬移
public void add(int index, E element) {
//判断指定位置不能小于0且不能超过数组大小范围
rangeCheckForAdd(index);
//是否需要扩容,与add(E)相同
ensureCapacityInternal(size + 1); // Increments modCount!!
//数组拷贝搬移(指定位置后的数据整体向后搬移一位)
//参数说明:源数组,在源数组中的起始位置,目表数组,目标数据中的起始位置,要复制的数组元素数
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));
}
根据下标获取元素对象:public E get(int index),根据下标获取数组元素
public E get(int index) {
//校验下标合法性
rangeCheck(index);
//根据下标获取元素对象
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
替换指定位置的元素:public E set(int index, E element);
public E set(int index, E element) {
//校验下标的合法性
rangeCheck(index);
//添加对象返回原对象
E oldValue = elementData(index);
//元素对象替换
elementData[index] = element;
return oldValue;
}
删除指定位置的元素:public E remove(int index);
public E remove(int index) {
//校验下标的合法性
rangeCheck(index);
//数组修改次数+1
modCount++;
//获取元素对象
E oldValue = elementData(index);
//获取删除元素的上一个元素的位置
int numMoved = size - index - 1;
if (numMoved > 0)
//搬移数组
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//删除元素后将数组末赋值为null,交给GC收集处理
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
删除指定元素: public boolean remove(Object o);
//循环变量数组,获取删除元素的位置
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//搬移数组元素,将末尾元素赋值为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
}
清除所有元素: public void clear();
//遍历所有元素并且赋值为null,size赋值为0.
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
合并两个容器元素:public boolean 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;
}
对比Vector
Vector是同步化的ArrayList。和ArrayList相比,Vector的在对数组的包装方法中增加了synchronized关键字,保证了多个线程访问的安全问题,但是对于单个线程访问时,锁的获取和释放就有点浪费性能了。所以在不需要考虑线程安全的场景下ArrayList是最佳的选择。
总结:
以上是ArrayList的几个重要的增加改查的方法,从源码中可以看出:
1.ArrayList的增删,需要遍历数组,并且会执行数组的搬移,时间复杂度为O(n);
2.ArrayList的查找时间复杂度为O(1)。
备注:JDK版本:1.8.0_241