上篇https://blog.csdn.net/friendlychen/article/details/79965762介绍了Java集合大家族的基本介绍,这里将具体介绍下ArrayList这个常用的List接口的实现类。
1、ArrayList概述
ArrayList作为Java集合的一个成员,是List接口的一个具体实现,底层是由数组的形式实现的,接下来从ArrayList源码角度分析下,JDK中每个版本对ArrayList都做了不同的优化,比如一个初始化的集合长度,在JDK1.8之后,默认是空的,在add的时候,会自动填充到10,而1.8之前是在初始化的时候就给了默认长度10。这里主要讨论1.8中的源码。
ArrayList是List的实现类。包含了添加,删除,替换等行为,ArrayList允许为null
2、可用的操作
**添加**
添加单个元素到尾部
public boolean add(E e) {}
添加单个元素到特定的位置
public void add(int index, E element) {}
添加整个集合到尾部
public boolean addAll(Collection<? extends E> c) {}
添加整个集合到特定的位置
public boolean addAll(int index, Collection<? extends E> c) {}
**删除**
删除对应下标的元素
public E remove(int index) {}
删除对应的元素
public boolean remove(Object o) {}
删除所有元素
public void clear() {}
删除集合中的跟参数集合中一样的元素
public boolean removeAll(Collection<?> c) {}
保留集合中的元素,删除其他。
public boolean retainAll(Collection<?> c) {}
**查询**
查询对应下标的元素
public E get(int index) {}
查询是否包含当前元素
public boolean contains(Object o) {}
查询元素的第一次出现的下标
public int indexOf(Object o) {}
查询元素最后一次出现的下标
public int lastIndexOf(Object o) {}
**替换**
public E set(int index, E element) {}
**其他常用接口**
返回当前数组元素数量
public int size() {}
判断当前数组元素是否为空
public boolean isEmpty() {}
转换成数组的形式
public Object[] toArray() {
转换成对象数组并且复制到原有数组中,若长度超过,则丢弃
public <T> T[] toArray(T[] a) {
两种迭代器
public ListIterator<E> listIterator() {
public Iterator<E> iterator() {
上面是双向迭代,下面是从开始到末尾一个个来
3、源码分析
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ }
ArrayList继承了AbstractList抽象类,AbstractList抽象类实现了List接口。
ArrayList实现了RandomAccess随机访问,Clone克隆,Serializable序列化和List的接口
RandomAccess就是个没有任何方法的接口,看源码注解,就是专门为List实现类使用的,用于支持快速随机访问的意思,就相当于实现了Serializable就等于支持序列化和反序列化,这是个标准。里面没有任何方法的。
public ArrayList(int initialCapacity) {
super();
//判断参数合法性,必须是大于0的
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
super();
//这里EMPTY_ELEMENTDATA是空集合
this.elementData = EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
三个构造方法,一个无参构造方法,无参默认是空集合,add添加的时候集合长度自动填充到10,一个int类型参数的构造方法,可以指定初始化数量,一个集合参数的构造方法
三个参数都对elementData进行初始化,那么这个elementData是什么呢?看变量声明,这就是一个数组,这就是为什么我们说ArrayList底层是数组结构的原因,我们操作ArrayList其实就是对这个elementData数组进行操作。
两个常量,一个是默认容量10
private static final int DEFAULT_CAPACITY = 10;
空集合
private static final Object[] EMPTY_ELEMENTDATA = {};
DEFAULT_CAPACITY,就是默认的容量,数组长度默认是10。
接下来是对集合的各种操作,添加,删除,查询,修改等等。
添加数据
**添加一个数据**
public boolean add(E e) {
//扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//典型的数组操作,把e元素添加到数组的第size++个位置上,
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//做了初始化操作,如果为空的话,则要么10 ,要么minCapacity,最少10
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//扩容
ensureExplicitCapacity(minCapacity);
}
//扩容的操作
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //fail-fast机制需要
//与原来数组长度比较,如果长了,则扩容
// 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); //原来数组长度加上现有数组长度除以2。>> 右移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:
//真正的数组拷贝,调用的是System.arraycopy()方法,这其实是相当于重新建一个数组,这种速度是很慢的,浪费时间空间。
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;
}
**删除源码**
public E remove(int index) {
//判断下标合法性
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//fail-fast机制需要
modCount++;
//获取到相应的元素,就是从数组中取出
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
//数组新建,
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//这里把最后一个元素置为null,就是移出了最后一个元素坐在的位置
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
**查找**
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//这里很简单就是elementData这个数组中下标为index的元素
return (E) elementData[index];
}
**查找元素下标**
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//其实也是循环数组,如果匹配,则返回下标,否则不匹配,返回的是-1
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
**替代**
public E set(int index, E element) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//找出那个对应的原色,然后进行替换
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
4、总结
最后看看modCount这个参数
我们知道ArrayList是线程不安全的,当多线程进行迭代操作时可能会产生冲突,此时,fail-fast,也就是错误检测机制,就可以预防这种错误。
modCount顾名思义就是修改次数;
当一个线程进行遍历集合时,另外一个线程修改了结构,比如添加add,删除remove元素时,modCount会自增1,这时程序每次迭代操作时,都会检测到这个modCount已经有变化了,有变化会抛出个ConcurrentModificationException异常,一般查询元素和修改元素是不会有这种异常的。(也就是如果不引起ArrayList结构变化的操作,一般是没问题的,删除和新增元素都引起了ArrayList结构的变化了)。
我们简单回顾下ArrayList源码,基本都是对elementData这个数组进行操作,比如新增元素时,其实调用的是Arrays.copyOf(elementData, newCapacity),我们知道已经分配好的数组的长度是不能变得,因此这个方法其实是新建了一个数组,然后把原来的数组的元素在复制一份过来,在把新元素插进去。这样我们就实现了ArrayList元素的增加。相信大家可以理解为什么我们说ArrayList底层是数组实现的原因了吧。接下来作为对比,看看底层作为链表形式的LinkedList的使用和源码简单分析。