在以前的文章中咱们提到过ArrayList,ArrayList能够说是每个学java的人使用最多最熟练的集合了,可是知其然不知其因此然。关于ArrayList的具体实现,一些基本的都也知道,譬如数组实现,线程不安全等等,可是更加具体的就不多去了解了,例如:初始化的长度,扩容等。java
本篇主要经过一些对源码的分析,讲解几个ArrayList常见的方法,以及和Vector的区别。算法
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 复制代码
ArrayList其实是一个动态数组,容量能够动态的增加,其继承了AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。数组
RandomAccess接口,被List实现以后,代表List提供了随机访问功能,也就是经过下标获取元素对象的功能。缓存
1.RandomAccess接口,标记接口,代表List提供了随机访问功能,也就是经过下标获取元素对象的功能。之因此是标记接口,是该类原本就具备某项能力,使用接口对其进行标签化,便于其余的类对其进行识别(instanceof)。 2.ArrayList数组实现,自己就有经过下标随机访问任意元素的功能。那么须要细节上注意的就是随机下标访问和顺序下标访问(LinkedList)的不一样了。也就是为何LinkedList最好不要使用循环遍历,而是用迭代器遍历的缘由。 3.实现RandomAccess同时意味着一些算法能够经过类型判断进行一些针对性优化,例子有Collections的shuffle方法,源代码就不粘贴了,简单说就是,若是实现RandomAccess接口就下标遍历,反之迭代器遍历 实现了Cloneable, java.io.Serializable意味着能够被克隆和序列化。安全
在使用中咱们常常须要new出来各类泛型的ArrayList,那么在初始化过程是怎样的呢?多线程
以下一行代码,建立一个ArrayList对象dom
List<Person> list = new ArrayList<>();
复制代码
咱们来看源码,是如何初始化的,找到构造方法工具
//无参构造方法
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
复制代码
看到这些代码的时候,我也是不解的,elementData和EMPTY_ELEMENTDATA是什么啊?可是很明显EMPTY_ELEMENTDATA是一个常量,追本溯源咱们去看一下成员属性。性能
//若是是无参构造方法建立对象的话,ArrayList的初始化长度为10,这是一个静态常量
private static final int DEFAULT_CAPACITY = 10;
//在这里能够看到咱们不解的EMPTY_ELEMENTDATA其实是一个空的对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//保存ArrayList数据的对象数组缓冲区 空的ArrayList的elementData = EMPTY_ELEMENTDATA 这就是为何说ArrayList底层是数组实现的了。elementData的初始容量为10,大小会根据ArrayList容量的增加而动态的增加。
private transient Object[] elementData;
//集合的长度
private int size;
复制代码
执行完构造方法,以下图优化
能够说ArrayList的做者真的是很贴心,连缓存都处理好了,屡次new出来的新对象,都执行同一个引用。
/** * Appends the specified element to the end of this list. */
//增长元素到集合的最后
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
//由于++运算符的特色 先使用后运算 这里其实是
//elementData[size] = e
//size+1
elementData[size++] = e;
return true;
}
复制代码
先无论ensureCapacityInternal的话,这个方法就是将一个元素增长到数组的size++位置上。
再说回ensureCapacityInternal,它是用来扩容的,准确说是用来进行扩容检查的。下面咱们来看一下整个扩容的过程
//初始长度是10,size默认值0,假定添加的是第一个元素,那么minCapacity=1 这是最小容量 若是小于这个容量就会报错
//若是底层数组就是默认数组,那么选一个大的值,就是10
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//调用另外一个方法ensureExplicitCapacity
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//记录修改的次数
modCount++;
// overflow-conscious code
//若是传过来的值大于初始长度 执行grow方法(参数为传过来的值)扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//真正的扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//新的容量是在原有的容量基础上+50% 右移一位就是二分之一
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:
//这里是重点 调用工具类Arrays的copyOf扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
//Arrays
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
复制代码
添加到指定的位置
public void add(int index, E element) {
//判断索引是否越界,若是越界就会抛出下标越界异常
rangeCheckForAdd(index);
//扩容检查
ensureCapacityInternal(size + 1); // Increments modCount!!
//将指定下标空出 具体做法就是index及其后的全部元素后移一位
System.arraycopy(elementData, index, elementData, index + 1,size - index);
//将要添加元素赋值到空出来的指定下标处
elementData[index] = element;
//长度加1
size++;
}
//判断是否出现下标是否越界
private void rangeCheckForAdd(int index) {
//若是下标超过了集合的尺寸 或者 小于0就是越界
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
复制代码
ArrayList支持两种删除元素的方式
下面咱们看一下ArrayList的实现
/** 移除list中指定位置的元素 * Removes the element at the specified position in this list. 全部后续元素左移 下标减1 * Shifts any subsequent elements to the left (subtracts one from their * indices). *参数是要移除元素的下标 * @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++;
//获取这个下标上的值
E oldValue = elementData(index);
//计算出须要移动的元素个数 (由于须要将后续元素左移一位 此处计算出来的是后续元素的位数)
int numMoved = size - index - 1;
//若是这个值大于0 说明后续有元素须要左移 是0说明被移除的对象就是最后一位元素
if (numMoved > 0)
//索引index只有的全部元素左移一位 覆盖掉index位置上的元素
System.arraycopy(elementData, index+1, elementData, index,numMoved);
// 将最后一个元素赋值为null 这样就能够被gc回收了
elementData[--size] = null; // clear to let GC do its work
//返回index位置上的元素
return oldValue;
}
//移除特定元素
public boolean remove(Object o) {
//若是元素是null 遍历数组移除第一个null
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
}
复制代码
transient修饰的属性意味着不会被序列化,也就是说在序列化ArrayList的时候,不序列化elementData。
为何要这么作呢?
因此说不是不序列化 而是不所有序列化。
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
复制代码
那么问题来了
ArrayList有没有办法线程安全?
Collections工具类有一个synchronizedList方法
能够把list变为线程安全的集合,可是意义不大,由于可使用Vector
Vector为何是线程安全的?
老实讲,抛开多线程 它俩区别没多大,可是多线程下就不同了,那么Vector是如何实现线程安全的,咱们来看几个关键方法
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
复制代码
就代码实现上来讲,和ArrayList并无不少逻辑上的区别,可是在Vector的关键方法都使用了synchronized修饰。