每一个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它老是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增加。自动增加会带来数据向新数组的从新拷贝,所以,若是可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可使用ensureCapacity操做来增长ArrayList实例的容量,这能够减小递增式再分配的数量。
注意,此实现不是同步的。若是多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。这一般是经过同步那些用来封装列表的对象来实现的。但若是没有这样的对象存在,则该列表须要运用{@link Collections#synchronizedList Collections.synchronizedList}来进行“包装”,该方法最好是在建立列表对象时完成,为了不对列表进行突发的非同步操做。php
下面咱们讨论下ArrayList初始默认容量的问题。
有文章说ArrayList默认构造的容量为10,没错。 由于ArrayList的底层是由一个Object[]数组构成的,而这个Object[]数组,默认的长度是10,因此有的文章会说ArrayList长度容量为10。 html
然而你所指的size()方法,指的是“逻辑”长度。
所谓“逻辑”长度,是指内存已存在的“实际元素的长度” 而“空元素不被计算” java
即:当你利用add()方法,向ArrayList内添加一个“元素”时,逻辑长度就增长1位。 而剩下的9个空元素不被计算。 web
以下代码:算法
ArrayList<String> list = new ArrayList<String>();
System.out.println("size = " + list.size());
输出结果以下:数组
size = 0
ArrayList默认size()是0.数据结构
JDK版本不同,ArrayList类的源码也不同。app
//经过ArrayList实现的接口可知,其支持随机访问,能被克隆,支持序列化
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//序列版本号
private static final long serialVersionUID = 8683452581122892189L;
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//被用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//被用于默认大小的空实例的共享数组实例。其与EMPTY_ELEMENTDATA的区别是:当咱们向数组中添加第一个元素时,知道数组该扩充多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/** * Object[]类型的数组,保存了添加到ArrayList中的元素。ArrayList的容量是该Object[]类型数组的长度 * 当第一个元素被添加时,任何空ArrayList中的elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA将会被 * 扩充到DEFAULT_CAPACITY(默认容量)。 */
transient Object[] elementData; //非private是为了方便嵌套类的访问
// ArrayList的大小(指其所含的元素个数)
private int size;
......
}
ArrayList包含了两个重要的对象:elementData 和 size。dom
elementData 是”Object[] 类型的数组”,它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,咱们能经过构造函数 ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;若是经过不含参数的构造函数ArrayList()来建立 ArrayList,则elementData的容量默认是10。elementData数组的大小会根据ArrayList容量的增加而动态的增加,具 体的增加方式,请参考源码分析中的ensureCapacity()函数。svg
size 则是动态数组的实际大小。
ArrayList提供了三种方式的构造器,能够构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回的顺序排列的。
/** * 构造一个指定初始容量的空列表 * @param initialCapacity ArrayList的初始容量 * @throws 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);
}
}
// 构造一个默认初始容量为10的空列表
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/** * 构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回的顺序排列的 * @param c 包含用于去构造ArrayList的元素的collection * @throws NullPointerException 若是指定的collection为空 */
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray()可能不会正确地返回一个 Object[]数组,那么使用Arrays.copyOf()方法
if (elementData.getClass() != Object[].class)
//Arrays.copyOf()返回一个 Object[].class类型的,大小为size,元素为elementData[0,...,size-1]
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList构造一个默认初始容量为10的空列表:
初始状况:elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; size = 0;
当向数组中添加第一个元素时,经过add(E e)方法中调用的ensureCapacityInternal(size + 1)方法,即ensureCapacityInternal(1);
在ensureCapacityInternal(int minCapacity)方法中,可得的minCapacity=DEFAULT_CAPACITY=10,而后再调用ensureExplicitCapacity(minCapacity)方法,即ensureExplicitCapacity(10);
在ensureExplicitCapacity(minCapacity)方法中调用grow(minCapacity)方法,即grow(10),此处为真正具体的数组扩容的算法,在此方法中,经过elementData = Arrays.copyOf(elementData, 10)具体实现了elementData数组初始容量为10的构造。
从add()与addAll()方法中能够看出,每当向数组中添加元素时,都要去检查添加元素后的个数是否会超出当前数组的长度,若是超出,数组将会进行扩容,以知足添加数据的需求。数组扩容实质上是经过私有的方法ensureCapacityInternal(int minCapacity) -> ensureExplicitCapacity(int minCapacity) -> grow(int minCapacity)来实现的,但在jdk1.8中,向用户提供了一个public的方法ensureCapacity(int minCapacity)使用户能够手动的设置ArrayList实例的容量,以减小递增式再分配的数量。此处与jdk1.6中直接经过一个公开的方法ensureCapacity(int minCapacity)来实现数组容量的调整有区别。
/** * public方法,让用户能手动设置ArrayList的容量 * @param minCapacity 指望的最小容量 */
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity) {
//当elementData为空时,ArrayList的初始容量最小为DEFAULT_CAPACITY(10)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//数组可被分配的最大容量;当须要的数组尺寸超过VM的限制时,可能致使OutOfMemoryError
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/** * 增长数组的容量,确保它至少能容纳指定的最小容量的元素量 * @param minCapacity 指望的最小容量 */
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//注意此处扩充capacity的方式是将其向右一位再加上原来的数,其实是扩充了1.5倍
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
附:jdk1.6中ensureCapacity(int minCapacity)方法:
// 肯定ArrarList的容量。
// 若ArrayList的容量不足以容纳当前的所有元素,设置 新的容量=“(原始容量x3)/2 + 1”
public void ensureCapacity(int minCapacity) {
modCount++; // 将“修改统计数”+1
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
为何ArrayList自动容量扩充选择扩充1.5倍?
这种算法构造出来的新的数组长度的增量都会比上一次大( 并且是愈来愈大) ,即认为客户须要增长的数据不少,而避免频繁newInstance 的状况。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
能够看到ArrayLis < E>是支持泛型的,因此ArrayList能够构形成任何类型的动态数组。
同时它也继承了AbstractList抽象类,抽象类实现了不少默认的方法,可是还有一些方法仍是抽象方法。
实现了通用的List列表接口,这里面定义了List列表的基础方法。
同时实现了RandomAccess,Cloneable,Serializable接口,这三个接口都是空接口,里面没有任何方法声明。
RandomAccess是一个标记接口,其主要就是提供给List接口用的,用来代表其支持快速随机访问。由于这个接口是没有任何实现的,实现了这个接口的类,就代表这个类支持快速访问,就至关于实现了Serializable就等于支持序列化和反序列化,这是个标准。
Cloneable接口,代表这个是能够进行浅拷贝的,是能够调用Object.clone()返回该对象的浅拷贝。
什么是浅拷贝呢?
举个例子:
假设x是一个飞空对象,应该有:
(x.clone()!=x )==true,也就是说它们不是同一个对象。
(x.clone().getClass()==x.getClass())==true,也就是它们是同一个类型的class,
(x.equals(x.clone()))==true,也就是,逻辑上和内容上,它们应该是相同的。
实现了Cloneable的类应该要知足上面的几个状况。Serializable接口是咱们常常遇到的接口,代表该类支持序列化和反序列化操做。
因此ArrayList继承这三个没有任何方法定义的接口只是为了代表这个类是支持随机快速访问的,能够支持浅拷贝的,能够被序列化和反序列化的。
都说ArrayList是动态数组,那么它里面存储数据的数据结构是什么呢?
private transient Object[] elementData;
上面这个对象数组就是其存储元素的数据结构,前面有一个java关键字transient,这个关键字是去序列化的意思,即,在这个类序列化后保存到磁盘或者输出到输出流的时候,这个对象数组是不被保存或者输出的。
这里又有疑问了,这个数组不是存储咱们保存的数据的吗?为何要去序列化呢?那么若是去掉序列化以后,咱们保存的元素从哪里来呢?
这就跟这个ArrayList的特性有关,咱们知道ArrayList的容量,也就是这个数组的容量,通常都是预留一些容量,等到容量不够时再拓展,那么就会出现容量还有冗余的状况,若是这时候进行序列化,整个数组都会被序列化,连后面没有意义空元素的也被序列化。这些是不该该被存储的。因此java的设计者,就为这个类提供了一个writeObject方法,在实现了Serializable接口的类,若是这个类提供了writeObject方法,那么在进行序列化的时候就会经过writeObject方法进行序列化,因此ArrayList的writeObject方法就会显式的为每一个实际的数组元素进行序列化,只序列化有用的元素。
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();
}
}
若是想了解更多有关java序列化的知识,请擢:http://zhh9106.iteye.com/blog/2152397
ArrayList的构造方法:
/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/** * Constructs an empty list with an initial capacity of ten. */
public ArrayList() {
this(10);
}
/** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */
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);
}
ArrayList提供了三个构造方法,第一个是由调用者传入指定List的大小来建立elementData数组。第二个是默认的构造方法,默认数组容量是10。第三个是根据传入的一个集合,将集合转化成数组,而后赋给elementData。
下面看一些ArrayList里面须要注意的方法。
/** * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The * elements themselves are not copied.) * * @return a clone of this <tt>ArrayList</tt> instance */
public Object clone() {
try {
@SuppressWarnings("unchecked")
ArrayList<E> v = (ArrayList<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
上面提到,ArrayList实现了Cloneable接口,支持返回浅拷贝,这里是重写了Object类的clone方法,返回的是一个副本(对象实例不一样,内容相同,由于对象实例都不一样,因此modCount也设为0)
/** * Removes the element at the specified position in this list. * 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;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
根据传入的index,而后利用System.arraycopy()方法将index后面的元素从index位置开始进行覆盖,这里须要注意的是,index+1到size位置的元素,覆盖掉index到size位置的元素,因此最后的两个元素确定相同,即重复了,因此最后一步会有elementData[–size] = null;将最后一个元素置为null。最后返回删除元素的值。
remove(Object o)与fastRemove(int index),这两个方法时一块儿使用的
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
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;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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; // Let gc do its work
}
这个是根据传入的元素来移除,首先找到要删除元素的位置,而后调用fastRemove()方法进行移除,这里你们会有疑问,既然找到index,为何不用上门那个移除方法处理呢?非要写个fastRemove(),看代码就知道,fastRemove跳过了index越界处理,而且不用返回要删除的元素值。
/** * Removes all of the elements from this list. The list will * be empty after this call returns. */
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
clear方法并非把整个数组都删除,由于毕竟已经申请了内存,这样删了,很惋惜,由于可能之后还用得着,这就免去了再次去申请内存的麻烦。这里的clear只是把每一个元素的都置为null,并把size设为0.
add(int index, E element)
/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
根据指定位置,插入指定元素,先进行越界检查,再进行容量检查,而后将从index开始的元素到最后的元素,从index+1位置开始日后复制,而后将指定元素插入到index位置,而后将容量size增长1。
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the
* specified collection's Iterator. The behavior of this operation is
* undefined if the specified collection is modified while the operation
* is in progress. (This implies that the behavior of this call is
* undefined if the specified collection is this list, and this
* list is nonempty.)
*
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
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;
}
在数组后面插入一个指定集合里面的全部元素,首先将集合转化为数组,而后进行容量检查,将目标数组元素拷贝到elementData上。这里有一点须要注意,若是传入的集合为null,那么结果会返回FALSE。
/** * Inserts all of the elements in the specified collection into this * list, starting at the specified position. Shifts the element * currently at that position (if any) and any subsequent elements to * the right (increases their indices). The new elements will appear * in the list in the order that they are returned by the * specified collection's iterator. * * @param index index at which to insert the first element from the * specified collection * @param c collection containing elements to be added to this list * @return <tt>true</tt> if this list changed as a result of the call * @throws IndexOutOfBoundsException {@inheritDoc} * @throws NullPointerException if the specified collection is null */
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;
}
将集合元素根据指定的位置开始插入,这个方法跟上面方法的区别是,要插入到指定位置,那么从源码能够看到,首先其会从原数组index开始到最后的元素,从index+numNew开始日后复制,目的是空出numNew个位置来存储目标集合的元素。而后再讲目标数组从index位置开始依次插入到原数组。
从上面的插入的几个方法会发现一个共同点,就是每次插入前都会进行容量检查,检查是否超出了容量,若是超出了,则进行扩容。那看看,它是若是进行扩容的?而这里也是jdk1.6和jdk1.7的实现区别。
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
根据传入的最小须要容量minCapacity来和数组的容量长度对比,若minCapactity大于或等于数组容量,则须要进行扩容,调用grow()
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */
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);
}
首先获得数组的旧容量,而后进行oldCapacity + (oldCapacity >> 1),将oldCapacity 右移一位,其效果至关于oldCapacity /2,咱们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,而后检查新容量是否大于最小须要容量,若仍是小于最小须要容量,那么就把最小须要容量看成数组的新容量,接着,再检查新容量是否超出了ArrayList所定义的最大容量,若超出了,则调用hugeCapacity()来比较minCapacity和MAX_ARRAY_SIZE,若是minCapacity大于最大容量,则新容量则为ArrayList定义的最大容量,不然,新容量大小则为minCapacity。还有一点须要注意的是,容量拓展,是建立一个新的数组,而后将旧数组上的数组copy到新数组,这是一个很大的消耗,因此在咱们使用ArrayList时,最好能预计数据的大小,在第一次建立时就申请够内存。
下面附上hugeCapacity()代码:
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
以上介绍了ArrayList几个须要注意的方法源码实现原理,上面咱们提到jdk1.6和jdk1.7在ArrayList上的实现区别就在于数组的容量扩展,以上代码都是jdk1.7的,下面来看看jdk1.6的容量扩展的实现:
/** * Increases the capacity of this <tt>ArrayList</tt> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
从代码上,咱们能够看出区别:
第一:在容量进行扩展的时候,其实例如整除运算将容量扩展为原来的1.5倍加1,而jdk1.7是利用位运算,从效率上,jdk1.7就要快于jdk1.6。
第二:在算出newCapacity时,其没有和ArrayList所定义的MAX_ARRAY_SIZE做比较,为何没有进行比较呢,缘由是jdk1.6没有定义这个MAX_ARRAY_SIZE最大容量,也就是说,其没有最大容量限制的,可是jdk1.7作了一个改进,进行了容量限制。
参考: