一、ArrayList
从名字上来看就是以数组为基础的List。来看源码:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 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 = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
/**
* 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) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
}
ArrayList底层基于数组elementData进行存储,默认初始容量为10。下面看下add方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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);//1.5倍扩容
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);
}
add为尾部添加,每次add前都要先校验下数组容量是不是够存放新加入的数据。当调用默认构造函数进行初始化时,elementData存的是一个空数组的引用,第一次添加时会进行扩容,容量就是前面提到的默认容量10。当以后每次到临界容量时,数组会进行1.5倍的扩容,最终调用的是System.arraycopy。而随机位置添加的add源码如下:
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++;
}
和尾部添加一样,要先进行容量检查,不够的需要扩容。随机添加还需要数组的copy操作,然后把新元素放到指定位置。
那删除操作呢?
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; // clear to let GC do its work
return oldValue;
}
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;
}
无论是删除指定位置还是指定元素,删除都是伴随着copy的操作。
而查询的话,由于ArrayList的底层存储是数组,所以对指定位置的查询是O(1)的。
综合ArrayList的增删改查操作可以看出:
1、底层存储结构是数组,默认初始大小是10,每次扩容是之前的1.5倍,调用System.arraycopy
2、添加操作每次都会进行容量检查,不够就进行扩容,如果是指定位置添加,那么还会伴随copy操作
3、删除操作(除去删除末尾元素)会进行copy操作
4、查找效率很高,O(1)
二、LinkedList
先看源码:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* Constructs an empty list.
*/
public LinkedList() {
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
}
LinkedList底层是一个双向链表,每个节点采用内部静态类定义的Node<E>来存储,first和last节点存储链表的头和尾。它没有初始容量这个概念,因为链表可以随时添加,添加时生成Node<E>对象就可以,无需提前分配。
看下LinkedList的添加操作是如何实现的:
/**
* Links e as last element.
*/
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++;
}
/**
* Inserts element e before non-null Node succ.
*/
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++;
}
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));
}
/**
* Returns the (non-null) Node at the specified element index.
*/
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;
}
}
add元素的话还是默认添加在尾部,在指定位置添加的话,如果不是末尾,会先查找需要添加节点的位置。看下查找位置的node函数可以发现,它会先判断位置index位于当前链表的前部还是后部,然后从头节点first或者尾节点last向中间开始查找,提升了链表的查找效率。
删除操作的实现就不贴出来了,就是链表上删除某个节点。
查找也是通过上面提到的node函数来实现。虽说每次不用遍历整个链表,只需遍历一半即可,但是如果链表本身长度很长,那查找的耗时还是很大的。
综合LinkedList的增删改查操作可以看出:
1、依托于双向链表,增加和删除元素都很方便,不需要copy操作,也不浪费空间
2、查找的负责度是O(n)
三、ArrayList和LinkedList比较
1、ArrayList基于动态数组,LinkedList基于双向链表
2、ArrayList增加和删除需要copy操作,效率不如LinkedList对节点的增加和删除
3、随机访问效率上,ArrayList远优于LinkedList
因此,增加和删除操作较多时建议采用LinkedList,查找操作较多时建议采用ArrayList。
以上,如有错误,还望指正。