1、ArrayList的简介
AttayList是List接口的主要实现类,是有序存储数据,并且数据可以重复的。
ArrayList的底层使用Object[]数组。
ArrayList具有高效的存储和查询能力,但是其高效性,也造成了线程的不安全性。
2、ArrayList的源码分析
2.1、ArrayList的构造方法
ArrayList有三个构造方法:
2.1.1、第一个构造方法:无参构造方法
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//当没有什么参数时,默认就是去 创建一个无参的构造方法,长度为 10
List<Object> list = new ArrayList();
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
//创建一个长度为 10 的集合
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.1.2、第二个构造方法:带一个 int 类型的构造方法
指定初始值
List<Object> objects = new ArrayList<>(0);
/*
1、当我们初始化的值得大小大于 0 的时候,就会创建一个指定长度的集合
2、当我们的初始化值 == 0时,会就创建一个默认的空数组的实例
*/
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);
}
}
2.1.3、第三个构造方法:
构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返会
/**
* 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();
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;
}
}
2.2ArrayList的扩容机制
这里就以ArrayList的无参构造方法为例子:
2.2.1、先来看看add()
方法
在添加之前会先调用ensureCapacityInternl方法
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
2.2.2、来到ensureCapacityInternl方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果 默认的初始化容量 > 最小的容量 那么将返回初始化容量,否则反之返回最小容量
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//返回最小容量
return minCapacity;
}
/**
*该方法为判断返回 10 ,还是minCapacity
*/
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
其中 minCapacity为最小容量,elementData为元素数据
当minCapacity为1时,DEFAULT_CAPACITY默认的容量为10,返回10;
2.2.3、ensureExplicitCapacity()
方法
//判断是否执行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//调用grow方法进行扩容,
grow(minCapacity);
}
分析:
- 当我们要add()方法进行第一个元素添加到ArrayList中,elementData.lenght为0,因为执行了ensuerCapacityInternal()方法,所以minCapacity的值为10。此时minCapacity - elementData.length > 0表达式成立,调用grow方法进行扩容。
- 当add()方法第2个元素进行添加到ArrayList中,由于最小的容量(minCapacity)为2,但是elementData.lenght为10,表达式minCapacity - elementData.length > 0不成立,所以不进行扩容。
- 以至于minCapacity的值如果都小于10的话,表达式(minCapacity - elementData.length > 0)一直不成立,所以不需要再次扩容。
直到minCapacity为11时,然后根据表的式(minCapacity - elementData.length > 0)进行扩容。
2.2.4、grow()方法,扩容机制的核心代码
private void grow(int minCapacity) {
// overflow-conscious code
//旧的容量 = 元素数据.长度
int oldCapacity = elementData.length;
//新的容量 = 旧的容量 + (旧的容量 右移一位(相当于 oldCapacity/2))
//将旧的容量 更新为旧的容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//判断 新的容量是否大于最小容量
if (newCapacity - minCapacity < 0)
//如果小于 就将 最小的容量 赋值给 新的容量
newCapacity = minCapacity;
//判断新的容量大于 MAX_ARRAY_SIZE时,执行hugeCapacity方法
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);
}
int newCapacity = oldCapacity + (oldCapacity >> 1),所以ArrayList每次扩容之后都是将容量更新为 oldCapacity 的1.5倍(当 oldCapacity 为偶数时就是1.5倍,否则就是 1.5倍左右)。
“>>”位运算: “>> 1” 相当于 除以2,右移n位相当于除以2的n次方。这里oldCapacity明显右移了 1 为,所以就是oldCapacity/2。对于大数据的2进制运算,位移运算符比那些普通的运算符要快很多,这些提供了效率,节省了资源。
2.2.4.1、举例测试grow(minCapacity)方法
- 当add第1个元素时,因为
oldCapacity
为0,所以就算是通过等式(oldCapacity(0) + (oldCapacity(0) >> 1))
运算之后的newCapacity
还是等于0,经过前面的两步,获取到新的容量。开始第一个if判断等式成立,newCapacity
为10。开始执行第二个if判断(newCapacity(10) - MAX_ARRAY_SIZE >0)
等式不成立。就不会执行hugeCapacity方法
。数组的容量为10,在 add()方法中elementData[size++]
数组元素大小加1,并且返回 true。 - 当add第11个元素时,由于
minCapacity(11)
大于DEFAULT_CAPACITY(10)
时,返回的是minCapacity(11)
,通过if(minCapacity - elementData.length > 0)
表达式的判断,需要再次扩容。来的grow()方法,通过前面的两步赋值得到newCapacity
为15,minCapacity为11,第一个if判断不成立。由于newCapacity
为15没有大于数组的最大size
第二个if判断也不成立,不会执行hugeCapacity()方法。数组扩容为15。
2.2.5、hugeCapacity()最大容量方法
在上面的grow()
方法中经常会通过if
判断:当newCapacity
的值 大于 MAX_ARRAY_SIZE
时就会执行hugeCapacity()
方法。
//获取最大的容量方法
private static int hugeCapacity(int minCapacity) {
//如果最小的容量小于 0的话
if (minCapacity < 0) // overflow
//抛出内存不足的异常
throw new OutOfMemoryError();
//通过三目运算 比较 最小容量 是否 大于 MAX_ARRAY_SIZE ? 如果大于 则返回 Integer的最大值,否则返回MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
2.3、System.arraycopy()和array.copyOf()方法
在阅读ArrayList的源码中,在add()方法、toArray()方法
等方法中都有这个两个方法的声影。
2.3.1、System.arraycopy()
方法
/**
*index :索引值
*element:元素数据
*/
public void add(int index, E element) {
//指定需要插入元素的位置,
//通过调用rangeCheckForAdd(index)方法
rangeCheckForAdd(index);
//调用ensureCapacityInternal(size + 1)方法来确保容量百分百够
ensureCapacityInternal(size + 1); // Increments modCount!!
//将自身进行复制一份
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
/**
* @param src 数组类型.
* @param srcPos 原数组的起始位置.
* @param dest 目标数组.
* @param destPos 目标数组的起始位置.
* @param length 数组的长度.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
发现 arraycopy是一个本地方法,是通过c语言实现的。
测试System.arraycopy()
方法
public class ArrayListDemoTest {
public static void main(String[] args) {
int[] a = new int[5];
a[0] = 2;
a[1] = 54;
a[2] = 28;
a[3] = 23;
System.arraycopy(a,1,a,1,3);
a[2] = 17;
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}
}
结果:
2 54 17 23 0
2.3.2、Arrays.copyOf()方法
/**
*以正确的顺序(从第一个元素到最后一个元素)返回一个包含此列表中的所有元素的数组。
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
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;
}
Arrays.copyOf()方法也是调用了System.arraycopy()方法;
测试Arrays.copyOf()
方法
public class ArrayListDemoTest {
public static void main(String[] args) {
int[] b = new int[4];
b[0] = 0;
b[1] = 1;
b[2] = 2;
b[3] = 3;
//将原有的数组进行扩容,并且复制其元素数据和大小
int[] a = Arrays.copyOf(b, 67);
System.out.println("a.length\t"+a.length);
System.out.println("将原先的数组b的元素数据和大小都复制给数组a,并且扩容数组容量:\t"+a[3]);
}
}
结果
a.length 67
将原先的数组b的元素数据和大小都复制给数组a,并且扩容数组容量: 3
2.3.3、连着之间的联系和区别
联系:
通过阅读两者之间的源码发现,Arrays.copyOf()方法内部是调用了System.arraycopy()方法;
区别:
arraycopy()
需要目标数组,将原有的数组拷贝的自定义的数组或者原数组,而且可以选择拷贝的启点和长度以及放入新数组中的位置。
copyOf()
是系统自动在内部新建一个数组,进行拷贝,并且返回该数组。
2.4、ensureCapacity()方法
当我们阅读源码时发现扩容六大将其中有五个都是通过什么方法进而执行的,只有这么一个方法是没有人调用的。上源码
public void ensureCapacity(int minCapacity) {
//当元素数据 不等于 默认的元素空容量数据时,那么minCapacity为 0,否则minCapacity为10
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()方法,进行扩容
ensureExplicitCapacity(minCapacity);
}
}
使用的环境,在出现 add 大量元素之前用ensureCapacity()
方法,以减少增量重新分配的次数
测试
没有使用ensureCapacity()的 add 元素,测试添加一千万个元素数据进集合中
public class EnsureCapacityTest {
public static void main(String[] args) {
ArrayList<Object> arrayList = new ArrayList<>();
final int N = 10000000;
long startTime = System.currentTimeMillis();
for (int i = 0; i < N; i++) {
arrayList.add(i);
}
long endTime = System.currentTimeMillis();
System.out.println("使用ensureCapacity方法前:"+(endTime-startTime));
}
}
结果:
使用ensureCapacity方法前:2571
使用了ensureCapacity()的 add 元素,测试添加一千万个元素数据进集合中
使用ensureCapacity方法前:1799
通过运行结果,我们可以看出向 ArrayList 添加大量元素之前最好先使用ensureCapacity
方法,以减少增量重新分配的次数
2.5、ArrayList的优缺点
优点
ArrayList底层是以数组实现,是一种随意访问模式,再加上实现了RandomAccess接口,因此在执行get()方法的时候效率很快。
缺点
数组里面(除了末尾)插入和删除效率不高,因此需要移动大量的元素。
ArrayList在小于扩容容量的情况下,增加操作效率非常的高。但是当需要添加的元素大于原有容量时,就需要进行扩容操作,这时候添加操作效率非常低:
- 需要先将原有的数组copy一份,将复制的数组赋值到新的一个数组中。
所以遇到数据量比较多 或者 需要频繁插入和删除操作的时候,效率就显得比较低下。当遇到上述的情况时,可以使用LinkedList来代替。