Java容器之LinkedList源码阅读(二)
与ArrayList使用数组存储元素不同的是,linkedList底层使用的是多个节点组成的双端链表,每个节点除了存储值之外,还维护了指向前一个与后一个节点的引用。
// 与ArrayList不同的是LinkedList少实现了一个支持随机访问的接口,多了一个支持双端队
列的接口。这里可看出相对ArrayList它的随机访问效率肯定是要低的,而且for这种获取数据的方
式肯定不是首选。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
// 记录linkedList存放元素的数量
transient int size = 0;
// 指向链表中第一个节点,也就是头节点。此节点的prev(前节点指针)指向null
transient Node<E> first;
// 指向链表中最后一个节点,尾节点。此节点的next(后节点指针)指向null
transient Node<E> last;
// 无参构造,创建一个空的链表
public LinkedList() {
}
// 有参构造、将一个指定的集合添加到LinkedList中,先完成初始化,在调用添加操作
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList节点类
// 链表中维护的节点类,item为存放的元素,next:后节点指针,prev:前节点指针
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新增操作
// 添加指定元素,通过调用linkLast()方法将元素添加在链表尾部
public boolean add(E e) {
linkLast(e);
return true;
}
// 根据位置添加元素,很多文章都说LinkedList新增,插入操作快,实际上通过指定位置执行新
增操作如果位置不是链表尾部,是需要做一次node()方法查询的,所以LinkedList的优势也是需分
具体情况的。
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
// 返回指定位置的节点信息,此处使用了二分查找法,size >>1 将查询指定位置操作缩小到一半
再进行遍历。如果在前半部,从头开始遍历,后半部则从尾开始遍历。虽然说链表针对此方法做了一
定优化,但是对于基数比较庞大的链表来说,循环的代价还是比较高的,这也是为什么LinkedList
的随机访问效率相对较低。
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;
}
}
// 从链表头部添加元素
public void addFirst(E e) {
linkFirst(e);
}
// 从链表尾部添加元素
public void addLast(E e) {
linkLast(e);
}
// 头部添加元素很简单,通过节点构造函数生成新节点,把新节点置成首节点,将新节点内的前
节点指针置为null,后节点指针指向原头部节点。原头部节点内的前节点指针指向新节点。
LinkedList的添加插入操作完全只需要对节点内的前后两个指针进行操作,所有效率是非常高的。
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
// 尾部添加元素,生成新节点,将新节点内后节点指针置为null,前节点指针指向原有尾节点。原
有尾节点内后节点指针由null改成指向新节点。
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) {
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++;
}
LinkedList删除操作
// 指定位置移除元素,同样需要调用node()方法执行一次查询。与指定位置添加元素类似,链表
对于指定位置操作都需要进行一次查询。操作效率相对较慢。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
// 移除指定元素,根据被移除节点内前后节点指针重新建立链接,与指定位置添加元素操作类似。
E unlink(Node<E> x) {
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;
}
// 移除头部元素,调用removeFirst()方法移除头节点。
public E remove() {
return removeFirst();
}
// 移除头节点
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
// 移除头节点,把下一个节点置为头节点。返回被移除节点元素值。
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
// 移除尾节点
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
// 移除尾节点,把上一个节点置为尾节点。返回被移除节点元素值。
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
// 清空链表,循环所有链表节点,依次清空节点内所有属性
public void clear() {
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
LinkedList查询操作
// 根据指定位置查询元素,需要调用node()方法进行查询。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
// 查询第一个节点元素。直接返回头部节点元素值。
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
// 查询最后一个节点元素,直接返回尾部节点元素值
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
LinkedList修改操作
// 根据指定位置替换值,调用node()方法查询,然后进行替换。
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
总结:LinketList中还有很多作为队列使用而设置的方法,此处就不一一列举了。一般大家都把ArrayList与LinkedList拿来比较,实际上两者效率的高低取决于如何正确使用这两者。