1.首先,他们的底层实现结构不同,ArrayList基于数组,LinkedList基于链表实现。
2.由于底层结构的不同,ArrayList更适合随机查找,LinkedList更适合添加、删除。他们查找、添加、删除在不同的场景下时间复杂读有所不同。
3.另外ArrayList和LinkedList都实现了List等接口,但是LinkedList还实现了Deque接口,所以LinkedList还可以用作队列。
以下是详细解析(只展示部分源码):
下面是展示ArrayList是基于数组的部分依据
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList底层的数组结构
transient Object[] elementData; // non-private to simplify nested class access
//数组中存放了多少元素,不是数组的长度。
private int size;
}
下面是LinkedList基于链表的部分依据:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
//链表的长度
transient int size = 0;
//链表的头结点
transient Node<E> first;
//链表的尾结点
transient Node<E> last;
//将结点定义为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;
}
}
}
两个集合都有一个查询操作的方法
对于ArrayList,因为其是基于数组实现的,根据索引可以直接查询到对应的数据,其时间复杂度为O(1)
//ArrayList
public E get(int index) {
//检查index是否合法
rangeCheck(index);
//数组下标返回元素
return elementData(index);
}
对于LinkedList,查找对应的index需要对链表进行遍历,如果index=0或index=size()-1 ,那么其查询时间复杂度与ArrayList的查询复杂度一样都为O(1)。当index越向链表长度的中间点靠近,其查询效率就会越来越低,最终达到O(n)。
//LinkedList
public E get(int index) {
//检查index的合法性
checkElementIndex(index);
//查找对应index的结点
return node(index).item;
}
//如果index处于链表的前半部分,则从头结点开始遍历;如果index处于链表的后半段,则从尾结点向前遍历。基础是因为其为双向链表
Node<E> node(int index) {
//size>>1 相当于 size/2
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;
}
}
因为增添和删除是两个相对立的操作,其时间复杂度有相似的地方,所以我们这里采用增添方法来对比两个集合的区别
public boolean add(E e) {
//确保数组的大小还能够放下一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
//数组末尾放元素
elementData[size++] = e;
return true;
}
public boolean add(E e) {
linkLast(e);
return true;
}
//在链表的末尾添加元素
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;
//链表的长度+1
size++;
//修改操作数+1 ,用于防止并发操作
modCount++;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
//确保数组大小能否装下一个元素,否则实现扩容机制
ensureCapacityInternal(size + 1); // Increments modCount!!
//将[index,size()-1]的数据移动(复制)到[index+1,size()] 的位置
//这里时间复杂度为O(n)
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//直接对index进行赋值
elementData[index] = element;
size++;
}
public void add(int index, E element) {
//检查下标index的合法性
checkPositionIndex(index);
//如果下标正好为尾结点下标+1,则表示在末尾添加元素
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++;
}
//根据下标查找对应的结点
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;
}
}
我们可以注意到ArrayList和LinkedList都实现了List接口,所以他们都是支持index进行访问数据的。另外LinkedList还实现了Queue接口,这就表示LinkedList还可以当作队列来使用,提供与队列相关的方法:
//LinkedList
addFirst(E e);
addLast(E e);
removeFirst();
removeLast();
//以上都是ArrayList不具有的方法