1.ArrayList是基于动态数组的数据结构实现,LinkedList是基于双向链表实现。
2.对于随机访问的get和set方法,ArrayList要优于LinkedList,因为ArrayList基于数组,获取任意元素的位置时间复杂度为O(1),LinkedList基于链表,时间复杂度则为O(n)。
3.对于新增和删除操作add和remove,LinkedList比较占优势,因为如果ArrayList不是在尾部进行新增和删除时数组会进行前移或后移。
在不指定容量时,构造一个初始容量为 10 的空数组,不过在第一次插入时才会分配初始容量为 10 的空数组。
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
因为LinkedList是基于双向链表,链表不需要分配连续的内存空间,所以无参构造中无任何操作。
/**
* Constructs an empty list
* */
public LinkedList() {}
可以看到通过下面的源码看到,ArrayList在尾部插入时,先去检查内部容量,若为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(不指定容量生产ArrayList对象时)则分配初始容量为 10 的空数组,若容量不足则进行扩容,扩容为原数组大小右移一位加上原数组大小,约为1.5倍,为什么是1.5倍呢,需要考虑如下情况:
1.扩容容量不能太小,防止频繁扩容,频繁申请内存空间 + 数组频繁复制
2.扩容容量不能太大,需要充分利用空间,避免浪费过多空间
为了能充分使用之前分配的内存空间,最好把增长因子设为 1
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
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);
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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++;
}
LinkedList的插入则是修改Node节点中的前驱节点(prev)与后驱节点(next)进行插入,若是尾部插入则next为null。
public boolean add(E e) {
linkLast(e);
return true;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
ArrayList删除时,也是使用了System.arraycopy()方法进行了数组的前移,LinkedList则是通过修改prev和next进行remove操作,故删除时LinkedList效率比ArrayList高。
ArrayList直接通过数组下标获取,LinkedList则是通过遍历的方式进行获取,不过并不是重头节点开始进行便利,为了提高效率,元素下标小于长度的一半则从头部开始遍历,否则从尾部开始遍历,这样便提高了遍历的效率。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}