jdk版本不同略微不一致。以下是jdk1.8源码
LlinkedList
为双向链表,每个节点包含prev、item、next
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;
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;
}
}
/**
* Links e as first element. 添加到首位
*/
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);//前驱节点为null,当前节点作为后继节点
first = newNode;
if (f == null)//当前头节点为空 表示没有任何元素。首尾都为新添加的元素
last = newNode;
else//当前头节点不为空 插入到头结点之前作为前驱节点
f.prev = newNode;
size++;
modCount++;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);//前驱节点为尾节点,后继节点为null
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
addFirst(E e) 、addLast(E e) 头尾新增对LinkedList内存开销是统一的, 主要是分配一个内部Entry对象。
/**
* 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++;
}
/**
* 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;//所有引用都设为null help gc
size--;
modCount++;
return element;
}
中间插入和删除的开销也是固定的,不会随着列表增加内存分配而增大
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);
//index<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;
}
}
查询需要全链表循环,双向链表从头部查询和从尾部查询结果是一致的。由此可看出,LinkedList不支持高效的随机元素访问
ArrayList
数组(线性表),动态分配内存
/**
* Default initial capacity.
* 调用add方法后且初始化未指定长度 扩容到10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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);
}
- 未指定长度新增数据时默认为10,每次括容oldCapacity + (oldCapacity >> 1)
- 数组的最大长度为Integer.MAX_VALUE - 8
- 一旦ArrayList的容量发生了变化,申请扩容和数据的复制,都会带来内存及时间上的开销
- 在知道容量大小的情况下,最好使用以下声明方式以减少开销
public ArrayList(int initialCapacity)
- 当然也可以使用trimToSize()对集合长度重新修整以节省内存资源
ArrayList和LinkedList在性能上各有优缺点:
LinkedList | ArrayList | |
get() | 循环遍历,复杂度O(n) | 直接读取下标,复杂度 O(1) |
add(E) | 添加到末尾,复杂度O(1) | 添加到末尾,复杂度O(1) |
add(index, E) | 先查询,后直接指针指向操作,复杂度O(n) | 后面的元素需要向后移动,复杂度O(n) |
remove() | 删除元素,直接指针指向操作,复杂度O(1) | 删除元素,后面的元素需要逐个移动,复杂度O(n) |
- LinkedList不支持高效的随机元素访问。
- 列表末尾增加一个元素。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
- 在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
- ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
当你的操作是在一列数据(较大的数据量)的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。