在Java中,我们经常使用到List这个类。这个类有多种实现,本文将着重分析其中最为常见的两个实现方式ArrayList和LinkedList。
其实从名字就不难猜到,ArrayList是基于数组的实现方式,LinkedList是基于链表的实现方式,但我们最好还是通过查看源代码进行更具体的分析。
首先查看两者的rep和creator部分:
ArrayLIst:
private static final long serialVersionUID = 8683452581122892189L;
/**
* 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;
}
/**
* 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) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
从上面的代码中,我们能看到ArrayList确实采用了基于数组的实现方式,rep中有用于记录List长度的size,还有三种形式的数组:
1)EMPTY_ELEMENTDATA是用于构造空数组的;
2)DEFAULTCAPACITY_EMPTY_ELEMENTDATA也是用于构造空数组的,但它与EMPTY_ELEMENTDATA是有区别的,当之后出现第一个元素加入该数组时,它会扩展为大小为DEFAULT_CAPACITY(默认设置为10)的一个数组,即它在首次增加元素时对数组增加的长度有要求;
3)elementData是真正用来存储List中所有元素的数组,若它等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则会在出现第一个元素加入该数组时,扩展为大小为DEFAULT_CAPACITY(默认设置为10)的一个数组。
接下来再看看ArrayList的creator,发现同样有三种方式:
1)传入一个int型参数initialCapacity,这个参数指定List的初始长度。若参数大于0,则直接将elementData设置为大小为initialCapacity的数组;若参数等于0,则将elementData设置为EMPTY_ELEMENTDATA这个空数组;其余情况下,将会抛出异常;
2)不传入参数,则直接将elementData设置为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,可在之后加入第一个元素时再进行扩展;
3)传入一个Collection,将按照Collection的iterator给定的顺序将元素放入数组,再将数组赋值给elementData(要进行类的检查,若Collection不是ArrayList,会先进行转换),若Collection为空,将elementData设置为EMPTY_ELEMENTDATA这个空数组。
LinkedList:
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() {
}
/**
* 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 LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
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也确实采用了基于链表的实现方式(而且是双向链表)——rep中有记录List长度的size,还有List中第一个和最后一个结点first和last。值得一提的是,Java中没有指针,但这里的实现原理类似于指针,不过是借助结点Node实现的,所以我们还是将LinkedList看做链表。
LinkedList的creator也比ArrayList简单,有两种:一种不传入参数的,创建空链表;一种传入一个Collection,将按照Collection的iterator给定的顺序将元素创建链表。
然后看一下最基本的增加单个元素的操作:
ArrayList:
/**
* 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;
}
/**
* 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++;
}
发现有两种增加元素的方式:
1)不指定位置,会在请求增加一个长度之后,直接加在List最后;
2)指定位置,先检查位置参数index的合法性,若合法再请求增加一个长度,将插入位置后面的元素依次向后挪动一个位置,最后将要增加的元素插入指定位置。
LinkedList:
/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 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++;
}
可以看见LinkedList直接将元素放入新的结点,并加在List的末尾。
再看一下删减单个元素的操作:
ArrayList:
/**
* 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; // clear to let GC do its work
return oldValue;
}
/**
* 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; // clear to let GC do its work
}
与增加方法对应,同样是两种删减方法:一种按参数index查找(需要检查参数合法性),一种按元素查找,若能找到,则需要将删除位置后面的元素依次向前挪动一个位置,把原本的最后一个元素置为null,再将List的长度减1。
LinkedList:
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If this list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* {@code i} such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns {@code true} 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 {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
按元素查找,若能找到,则需要将删除位置前面和后面的结点(如果都有的话)链接起来,否则只需要修改单个结点的“指针”即可。
到此我们可以得出结论,ArrayList是基于数组的实现方式,LinkedList是基于链表的实现方式。结合源代码分析,我们还能发现在增删元素时,LinkedList性能会更好,因为不会涉及到移动元素,而ArrayList需要大量移动元素的操作。这启示我们在需要频繁修改List中元素的情境下,可以优先考虑LinkedList。