java Linkedlist
Linkedlist底层原理
LinkedList的底层结构是基于双向链表实现的,优势是插入数据效率很高,缺点是遍历数据效率低。LinkedList没有长度限制,所以不需要提供初始化大小的构造方法。
功能方面:
1)查找方面先是在双向链表里找到节点的位置index,找到之后,再对这个节点进行一系列操作,双向链表在查找index位置的节点时,如果index<双向链表长度的1/2,那么就从前面往后面查找,反之,则从哪个后面往前面查找。
2)关于删除数据,双向链表只要先查找到指定位置的index,然后执行node.previous.next = node.next;node.next.previous = node.previous;node = null的操作,就可以将这个节点移出链表,不需要对其它的节点进行更改,所以删除数据的效率很高。
3)关于增加数据,和删除数据差不多,都是先查找指定位置的index,然后执行previous.next=node;node.previous=previous;next.previous=node;node.next=next;node=value;就可以将新节点插入链表
4)遍历数据效率很低,因为每次都要从头结点开始查找,因此,查找并且获取指定位置数据的速度比较慢
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;
}
}
添加元素的方式
Node是在LinkedList中定义的一个静态内部类,代表了链表中节点的基本结构,一个完整的Node节点包含数据域item,前驱指针prev,和后继指针next。
以下是插入表头节点的实现源码
private void linkFirst(E e) {
final Node<E> f = first;
//当前节点的前驱指为null,后继节点指向原来的头节点
final Node<E> newNode = new Node<>(null, e, f);
//让头指针指向当前插入的新头结点
first = newNode;
//如果原来有头节点,则更新原来节点的前驱指针,否则更新尾指针
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
add方法
public boolean add(E e) {
//默认向链表尾部添加节点
linkLast(e);
return true;
}
在链表尾部添节点的原理类似
源码为:
void linkLast(E e) {
final Node<E> l = last;
//当前节点的前驱指向尾节点,后继指向 null
final Node<E> newNode = new Node<>(l, e, null);
//尾节点指向新插入的节点
last = newNode;
//如果原来有尾节点,则更新原来节点的后继指针,否则更新头指针
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
在指定index位置插入节点的方式
在指定节点前插入一个节点,将指定节点作为当前节点的后继,指定节点的前驱节点为当前节点的前驱,然后,修改指定节点的前驱为当前节点,以及指定节点的前驱节点的后继为当前节点。源码如下:
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++;
}
删除节点源码
public boolean remove(Object o) {
//遍历列表,找到item == o 的节点,在调用unlink()方法删除
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;
}
unlink方法
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;
}
总结:
LinkedList,可以当做集合、队列和栈来使用,由于底层是双向链表,因此随机访问性比较差,插入和删除数据的效率特别高。如果需要经常的插入、删除操作可以考虑使用LinkedList集合。如果要遍历LinkedList的元素,应该采用迭代器来实现。LinkedList是线程不安全的集合,如果多个线程同时访问,那么可以使用Collections将集合包装成线程安全的集合。