ArrayList 底层数据结构为 动态数组 ,因此咱们能够将之称为数组队列。 ArrayList 的依赖关系:html
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
复制代码
从依赖关系能够看出,ArrayList 首先是一个列表,其次,他具备列表的相关功能,支持快速(固定时间)定位资源位置。能够进行拷贝操做,同时支持序列化。这里咱们须要重点关注的是 AbstractLit 以及 RandomAccess 。这个类,一个是定义了列表的基本属性,以及肯定咱们列表中的常规动做。而RandomAccess 主要是提供了快速定位资源位置的功能。java
/**
* Default initial capacity.数组默认大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
空队列
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
若是使用默认构造方法,则默认对象内容是该值
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
用于存储数据
*/
transient Object[] elementData;
// 当前队列有效数据长度
private int size;
// 数组最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
复制代码
在ArrayList 的源码中,主要有上述的几个成员变量:数组
拓展思考: EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 都是两个空的数组对象,他们到底有什么区别呢?咱们在下一节讲解构造方法的时候,会作详细对比。安全
ArrayList 中提供了三种构造方法:bash
根据构造器的不一样,构造方法会有所区别。咱们在日常开发中,可能会出如今默认构造器内部调用了 ArrayList(int capacity) 这种方式,可是ArrayList 中对于不一样的构造器的内部实现都有所区别,主要跟上述提到的成员变量有关。数据结构
在源码给出的注释中这样描述:构造一个初始容量为十的空列表多线程
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
复制代码
从源码能够看到,它只是将 elementData 指向了 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的存储地址,而 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 实际上是一个空的数组对象,那么它为何说建立一个默认大小为10 的列表呢?dom
或者咱们从别的角度思考一下,若是这个空的数组,须要添加元素,会怎么样?函数
public boolean add(E e) {
ensureCapacityInternal(size + 1); //确认内部容量
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 若是elementData 指向的是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的地址
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
设置默认大小 为DEFAULT_CAPACITY
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//肯定实际容量
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 若是超出了容量,进行扩展
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);
}
复制代码
上述代码块比较长,这里作个简单的总结:ui
一、add(E e):添加元素,首先会判断 elementData 数组的长度,而后设置值
二、ensureCapacityInternal(int minCapacity):判断 element 是否为空,若是是,则设置默认数组长度
三、ensureExplicitCapacity(int minCapacity):判断预期增加数组长度是否超过当前容量,若是过超过,则调用grow()
四、grow(int minCapacity):对数组进行扩展
回到刚才的问题:为何说建立一个默认大小为10 的列表呢?或许你已经找到答案了~
复制代码
根据指定大小初始化 ArrayList 中的数组大小,若是默认值大于0,根据参数进行初始化,若是等于0,指向EMPTY_ELEMENTDATA 内存地址(与上述默认构造器用法类似)。若是小于0,则抛出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);
}
}
复制代码
拓展思考:为何这里是用 EMPTY_ELEMENTDATA 而不是跟默认构造器同样使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA ?有兴趣的童鞋能够本身县思考,通过思考的知识,才是你的~
将Collection<T> c 中保存的数据,首先转换成数组形式(toArray()方法),而后判断当前数组长度是否为0,为 0 则只想默认数组(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;
}
}
复制代码
上述的三个构造方法能够看出,其实每一个构造器内部作的事情都不同,特别是默认构造器与 ArrayList(int initialCapacity) 这两个构造器直接的区别 ,咱们是须要作一些区别的。
所以 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 与 EMPTY_ELEMENTDATA 的本质区别就在于,会不会设置默认的数组长度。
ArrayList 添加了四种添加方法:
首先看add(T t)的源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 元素个数加一,而且确认数组长度是否足够
elementData[size++] = e; //在列表最后一个元素后添加数据。
return true;
}
复制代码
结合默认构造器或其余构造器中,若是默认数组为空,则会在 ensureCapacityInternal()方法调用的时候进行数组初始化。这就是为何默认构造器调用的时候,咱们建立的是一个空数组,可是在注释里却介绍为 长度为10的数组。
public void add(int index, E element) {
// 判断index 是否有效
rangeCheckForAdd(index);
// 计数+1,并确认当前数组长度是否足够
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //将index 后面的数据都日后移一位
elementData[index] = element; //设置目标数据
size++;
}
复制代码
这个方法其实和上面的add相似,该方法能够按照元素的位置,指定位置插入元素,具体的执行逻辑以下:
1)确保数插入的位置小于等于当前数组长度,而且不小于0,不然抛出异常
2)确保数组已使用长度(size)加1以后足够存下 下一个数据
3)修改次数(modCount)标识自增1,若是当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增加数组
4)grow方法会将当前数组的长度变为原来容量的1.5倍。
5)确保有足够的容量以后,使用System.arraycopy 将须要插入的位置(index)后面的元素通通日后移动一位。
6)将新的数据内容存放到数组的指定位置(index)上
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;
}
复制代码
addAll() 方法,经过将collection 中的数据转换成 Array[] 而后添加到elementData 数组,从而完成整个集合数据的添加。在总体上没有什么特别之初,这里的collection 可能会抛出控制异常 NullPointerException 须要注意一下。
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)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
复制代码
与上述方法相比,这里主要多了两个步骤,判断添加数据的位置是否是在末尾,若是在中间,则须要先将数据向后移动 collection 长度 的位置。
ArrayList 中提供了 五种删除数据的方式:
删除数据并不会更改数组的长度,只会将数据重数组种移除,若是目标没有其余有效引用,则在GC 时会进行回收。
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; // 让指针最后指向空,进行垃圾回收
return oldValue;
}
复制代码
这种方式,会在内部进行 AccessRandom 方式遍历数组,当匹配到数据跟 Object 相等,则调用 fastRemove() 进行删除
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;
}
复制代码
fastRemove( ): fastRemove 操做与上述的根据下标进行删除实际上是一致的。
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
}
复制代码
该方法主要删除了在范围内的数据,经过System.arraycopy 对整部分的数据进行覆盖便可。
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;
}
复制代码
直接将整个数组设置为 null ,这里不作细述。
主要经过调用:
private boolean batchRemove(Collection<?> c, boolean complement) {
//获取数组指针
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
//根据 complement 进行判断删除或留下
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// 进行数据整理
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
复制代码
在retainAll(Collection c)也有调用,主要做用分别为,删除这个集合中所包含的元素和留下这个集合中所包含的元素。
清楚ArrayList 的删除方法后,再结合咱们经常使用的删除方式,进行思考,到底哪些步骤会出问题,咱们一般会选择变量列表,若是匹配,则删除。咱们遍历的方式有如下几种:
避免 ConcurrentModificationException 的有效办法是使用 Concurrent包下面的 CopyOnWriteArrayList ,后续会进行详细分析
ArrayList提供了2个toArray()函数:
调用 toArray() 函数会抛出“java.lang.ClassCastException”异常,可是调用 toArray(T[] contents) 能正常返回 T[]。
toArray() 会抛出异常是由于 toArray() 返回的是 Object[] 数组,将 Object[] 转换为其它类型(如如,将Object[]转换为的Integer[])则会抛出“java.lang.ClassCastException”异常,由于Java不支持向下转型。
toArray() 源码:
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
复制代码
若是咱们在开发过程当中有须要获取集合中的某一部分的数据进行操做,咱们能够经过使用SubList() 方法来进行获取,这里会建立ArrayList 的一个内部类 SubList()。
SubList 继承了 AbstractList,而且实现了大部分的 AbstractList 动做。
须要注意的是,SubList 返回的集合中的某一部分数据,是会与原集合相关联。即当咱们对Sublist 进行操做的时候,其实仍是会影响到原始集合。 咱们来看一下 Sublist 中的 add 方法:
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
复制代码
能够看到,Sublist 中的 加操做,其实仍是调用了 parent(也就是原集合) 中的加操做。因此在使用subList方法时,必定要想清楚,是否须要对子集合进行修改元素而不影响原有的list集合。
ArrayList整体来讲比较简单,不过ArrayList还有如下一些特色:
ArrayList本身实现了序列化和反序列化的方法,由于它本身实现了 private void writeObject(java.io.ObjectOutputStream s)和 private void readObject(java.io.ObjectInputStream s) 方法
ArrayList基于数组方式实现,无容量的限制(会扩容)
添加元素时可能要扩容(因此最好预判一下),删除元素时不会减小容量(若但愿减小容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间。
线程不安全
add(int index, E element):添加元素到数组中指定位置的时候,须要将该位置及其后边全部的元素都整块向后复制一位
get(int index):获取指定位置上的元素时,能够经过索引直接获取(O(1))
remove(Object o)须要遍历数组
remove(int index)不须要遍历数组,只需判断index是否符合条件便可,效率比remove(Object o)高
contains(E)须要遍历数组
使用iterator遍历可能会引起多线程异常
http://www.cnblogs.com/skywang12345/p/3308556.html https://blog.csdn.net/daye5465/article/details/77971530 https://blog.csdn.net/daye5465/article/details/77971530