1.ArrayList是什么?
先来看一下源码和体系结构:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList是我们使用频率最高的一个容器,它本质上是一个动态数组,底层就是一个数组的数据结构,是Array的一个加强版本,它支持动态的增加删除元素,同时可以灵活的设置数组的大小,看源码可以知道,它继承于AbstractList抽象类,实现了List,RandomAccess,Cloneable,Serializable接口,说明它属于List分支,并且支持快速随机访问,克隆和序列化功能.但是ArrayList支持的序列化和反序列化以及克隆方式都是自己实现的,详细内容可以去看它的readObject和writeObject以及clone方法.还有重要的就是ArrayList是非线程安全的
2.ArrayList怎么实现的?
这里从我们使用的角度去解析一下:
创建ArrayList
它的创建方式(构造器)有三种
//默认参数
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
private int size;
//设置容量
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);
}
}
//默认
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//填充
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;
}
}
我们常用的基本就是前两种了,从这里我们可以看出来,他其实就是new了一个长度大于等于0的Object[].下面来解析一些常用的api
新增元素add
public boolean add(E e) {//复杂度O(1)
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {//复杂度O(n)
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
咱们继续分析一下,如果指定下标新增数据的时候,会先检查下标是否符合规则,如果不符合规则会抛出IndexOutOfBoundsException异常,之后和不指定下标类似,指定下标的时候最终调用的是本地的方法,而不指定下标的add方法中用到了ensureCapacityInternal方法,这个方法的作用就是在添加元素的时候判断容量大小,如果长度超过10就用设定的长度为新长度,如果没有超过10就默认为10,而add方法中的核心就是grow方法,这个方法就是扩容,来看一下代码:
private static final int DEFAULT_CAPACITY = 10;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //右移1位相当于oldCapacity/2
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) throw new OutOfMemoryError();// overflow
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
从这个扩容代码里可以看出来,扩容的新容量是原来的1.5倍,最小长度为10,最大长度为int类型的最大值,大家都知道数组是个固定长度的,所以底层其实也是通过Arrays.copyOf方法复制了一个新长度数组而已.
删除元素remove 复杂度O(n)
了解了新增,现在在看看删除方法:
//删除单个位置的元素
public E remove(int index) {
rangeCheck(index);//检查下标
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)//删除的不是最后一个元素
System.arraycopy(elementData, index+1, elementData, index,numMoved);//删除的元素到最后的元素整块前移
elementData[--size] = null; //将最后一个元素设为null,在下次gc的时候就会回收掉了
return oldValue;
}
//从前向后移除第一个出现的元素o
public boolean remove(Object o) {
if (o == null) {//移除对象数组elementData中的第一个null
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {//移除对象数组elementData中的第一个o
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; //将最后一个元素设为null,在下次gc的时候就会回收掉了
}
同样的,删除也有两种方式,一种是删除指定位置上的元素,删除后从被删除元素位置到最后的元素整体往前移动一位.而直接删除元素的方法则是删除第一个元素后删除的元素到最后的元素整体向前移动一位.但是容量不会减少,若希望减少容量使用trimToSize()就可以实现.
更改元素
/**
* 更换特定位置index上的元素为element,返回该位置上的旧值
*/
public E set(int index, E element) {
RangeCheck(index);//检查索引范围
E oldValue = (E) elementData[index];//旧值
elementData[index] = element;//该位置替换为新值
return oldValue;//返回旧值
}
/**
* 检查索引index是否超出size-1
*/
private void RangeCheck(int i ndex) {
if (index >= size)
throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
}
这个方法感觉用的不是很频繁,这里对index进行了索引检查,是为了将异常内容写的详细一些并且将检查的内容(index<0||index>=size,注意这里的size是已存储元素的个数);事实上不检查也可以,因为对于数组而言,如果index不满足要求(index<0||index>=length,注意这里的length是数组的容量),都会直接抛出数组越界异常,而假设数组的length为10,当前的size是2,你去计算array[9],这时候得出是null,这也是上边get为什么减小检查范围的原因.
查找元素get,indexOf,lastIndexOf,contains
下面看一下源码其实也就懂了,没有什么很难的地方:
//get 获取 复杂度 O(1)
public E get(int index) {
rangeCheck(index);//检查下标
return elementData(index);
}
//indexOf 查找(需要遍历)
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;
}
//lastIndexOf 从后查找(需要遍历)
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//contains 包含,调用indexOf(需要遍历)
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
3.ArrayList优缺点?
从上边的分析我们可能会想到点什么,我这里总结一下:
优点:
1、ArrayList的优点在于可以顺序存储,随机存取,数据元素与位置相关联,因此查找效率高,索引遍历快.
2、在数组的基础上封装了对元素操作的方法。
3、可以自动扩容。
缺点:
1、线程不安全,插入与删除慢,需要通过移动元素来实现,因此效率低。
2、根据内容查找元素的效率较低。
注意点:
1.频繁的扩容对于ArrayList来说是比较消耗性能的,所以根据实际需求直接设定好合适的容量可以提高执行的效率.
2.ArrayList是非线程安全的,在多线程环境下如果想保证安全性,可以用Collections.synchronizedList()去实现.