前言:
一.在其他播客上看到下面这段话,可以说是总结的非常精辟了。读者们可以仔细品味:
ArrayList和LinkedList在性能上各 有优缺点,都有各自所适用的地方,总的说来可以描述如下:
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是 统一的,分配一个内部Entry对象。
2.在ArrayList的 中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
3.LinkedList不 支持高效的随机元素访问。
4.ArrayList的空 间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
可以这样说:当操作是在一列 数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中 间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是对其它指定位置的插入、删除操作,最好选择LinkedList
二:下面我从源码上分析ArrayList:
属性:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 序列化 ID
private static final long serialVersionUID = 8683452581122892189L;
/**
* ArrayList 默认的数组容量
*/
private static final int DEFAULT_CAPACITY = 10;
// 一个默认的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 在调用无参构造方法的时候使用该数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储 ArrayList 元素的数组
// transient 关键字这里简单说一句,被它修饰的成员变量无法被 Serializable 序列化
transient Object[] elementData; // non-private to simplify nested class access
// ArrayList 的大小,也就是 elementData 包含的元素个数
private int size;
}
实现了 Serializable 是序列化接口,因此它支持序列化,能够通过序列化传输。
实现了 Cloneable 接口,能被克隆。
实现了Iterable 接口,可以被迭代器遍历
实现了 Collection ,拥有集合操作的方法
实现了 List 接口,拥有增删改查等方法
实现了 RandomAccess 随机访问接口,支持快速随机访问,实际上就是通过下标序号进行快速访问。
构造方法:
可以看到elementData 数组 就是ArrayList用来存储数据的数组。
// 指定大小的构造方法,如果传入的是 0 ,直接使用 EMPTY_ELEMENTDATA
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 的数组,但是此时大小未指定,
// 还是空的,在第一次 add 的时候指定
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 传入一个集合类
// 首先直接利用Collection.toArray()方法得到一个对象数组,并赋值给elementData
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray 出错的时候,使用Arrays.copyOf 生成一个新数组赋值给 elementData
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果集合c元素数量为0,则将空数组EMPTY_ELEMENTDATA赋值给elementData
this.elementData = EMPTY_ELEMENTDATA;
}
}
方法:
我们重点说明 addAll 方法,涉及到ArrayList的数组扩容原理。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
1. add方法调用ensureCapacityInternal()方法,可以看到如果初始大小为{},则数组默认大小为10,继续调用
ensureExplicitCapacity()方法。
private void ensureCapacityInternal(int minCapacity) {
// 如果创建 ArrayList 时候,使用的无参的构造方法,那么就取默认容量 10 和最小需要的容量(当前 size + 1 )中大的一个确定需要的容量。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
2. 主要代码是 :
int newCapacity = oldCapacity + (oldCapacity >> 1);
这里表示新的数组大小为 原数组大小 + 原数组大小向右位移 1 位。后面我专门做右位移运算讲解。
private void ensureExplicitCapacity(int minCapacity) {
// 修改 +1
modCount++;
// 如果 minCapacity 比当前容量大, 就执行grow 扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 拿到当前的容量
int oldCapacity = elementData.length;
// oldCapacity >> 1 意思就是 oldCapacity/2,所以新容量就是增加 1/2.
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新容量小于,需要最小扩容的容量,以需要最小容量为准扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大于允许的最大容量,则以 Inerger 的最大值进行扩容
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 使用 Arrays.copyOf 函数进行扩容。
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 允许的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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的数组扩容原理介绍:
到这里我们可以看到 当我们创建一个ArrayList集合时。elementData 数组初始大小为0,当我们添加一个元素时elementData 大小为默认大小10,集合大小超过默认大小时,elementData 为之前的1.5倍大小,实际上是原数组大小 + 原数组大小右移1位。
下面我们来做一次计算:当我不停的往ArrayList 添加数据时:
List<String> list = new ArrayList<String>();
System.out.println(list);
for(int i = 0; i<100; i++) {
list.add("a");
}
System.out.println(list);
List<String> list = new ArrayList<String>(); //elementData 大小为0
第一次添加时 //elementData 大小为10
list.size()>10时 // 10的二进制大小 为 1010 右移一位大小为 0101,二级制 0101 的大小为5,所以elementData 大小为15
list.size()>15时 // 10的二进制大小 为 1111 右移一位大小为 0111,二级制 0111 的大小为7,所以elementData 大小为22
所以 当我 创建一个ArrayList时
当我add一个数据时 elementData大小为10
当集合大小超过10的时候,elementData大小为15
当集合大小超过15时,elementData大小为22
至此,验证成功。