数据结构
如图所示,LinkedList 底层是基于双向链表实现的,LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
类图:
源码分析
重要成员
transient int size = 0;
/**
* Pointer to first node.
*/
transient Node<E> first;
/**
* Pointer to last node.
*/
transient Node<E> last;
Node静态内部类
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;
}
}
add方法
添加到链尾
/**
* Appends the specified element to the end of this list.
*/
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++;
}
可见每次插入都是移动指针,和 ArrayList 的拷贝数组来说效率要高上不少。
添加到链表指定位置
/**
* Inserts the specified element at the specified position in this list.
*/
public void add(int index, E element) {
checkPositionIndex(index);//检查位置是否合法
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* 插入到指定链表位置
* 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++;
}
可见添加到指定位置,需要遍历n/2找到指定位置的元素,然后移动指针。
查询方法
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);
// 如果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;
}
}
上述代码,利用了双向链表的特性,如果index离链表头比较近,就从节点头部遍历。否则就从节点尾部开始遍历。使用空间(双向链表)来换取时间。
node()会以O(n/2)的性能去获取一个结点。这样的效率是非常低的,特别是当 index 越接近 size 的中间值时。
序列化
LinkedList自定义了序列化方式
/**
* Saves the state of this {@code LinkedList} instance to a stream
* (that is, serializes it).
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size 写入链表长度
s.writeInt(size);
// Write out all elements in the proper order. 依次写入每个节点元素
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
/**
* Reconstitutes this {@code LinkedList} instance from a stream
* (that is, deserializes it).
*/
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
Deque
LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问、插入、移除和检查元素的方法。
每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
如下:
FIFO
LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,下表的方法等价:
队列方法 | 等效方法 |
---|---|
add(e) | addLast(e) |
offer(e) | offerLast(e) |
remove() | removeFirst() |
poll() | pollFirst() |
element() | getFirst() |
peek() | peekFirst() |
LIFO
LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,下表的方法等价:
栈方法 | 等效方法 |
---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
总结
LinkedList 插入,删除都是移动指针效率很高。
查找需要进行遍历查询,效率较低