LinkedList 在什么情况下用到?它的数据结构是什么样的,和我们常常用到的 Arraylist 又有啥不同,接下来我们一起来学习一下在 LinkedList 源码中是怎么样实现的。(如果对ArrayList的实现还不太清楚的同学可以看一下ArrayList 数据结构分析)
LinkedList
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
复制代码
-
继承AbstractSequentialList<E>类,
AbstractSequentialList 继承自 AbstractList,是 LinkedList 的父类,是 List 接口 的一种简化版的实现。是一个抽象类 ,子类需要实现实现
public abstract ListIterator<E> listIterator(int index);
复制代码
该类通过ListIterator 来实现add,remove,addAll等方法。 实现可变的list需要实现iterator的set方法,实现可变list要实现iterator的remove方法。
-
实现了List接口
表明数据结构是 一个元素有序,可重复,可为null的集合,实现List接口的类,元素通过索引进行排序
-
实现了Deque接口
Deque extends Queue 表明是一个队列,或栈结构。Deque 即双端队列。是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。
-
实现了Cloneable接口
表明允许clone
-
实现了Serizlizable接口
序列化
单链表
双链表
LinkedList是一个双向链表的实现方式,逻辑上的相连关系。
属性
大小
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;
复制代码
add方法
//add方法其实就是在末尾插入一个新的节点
public boolean add(E e) {
linkLast(e);
return true;
}
复制代码
我们来看看linkLast的实现
//先看一下结点类的实现
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 last element.
*/
void linkLast(E e) {
<!--首先取出当前的最后一个结点保存,当做插入结点的前结点-->
final Node<E> l = last;
<!--创建出一个新的结点,newNode.prev为上一个末尾的结点,即l,e为插入的新的元素,newNode.next=null表示该结点是一个尾结点-->
final Node<E> newNode = new Node<>(l, e, null);
<!--将尾指针指向新加的节点(尾结点)-->
last = newNode;
<!--l==null表是插入的是一个头结点,将头指针指向新的结点(头结点)-->
if (l == null){
first = newNode;
}
else{
<!--将前一个结点的next指向新的尾结点-->
l.next = newNode;
}
<!--大小自增-->
size++;
<!--数据结构变动计数自增-->
modCount++;
}
复制代码
add(int index, E element) 方法
在指定位置插入元素
- s.pre = p.pre;
- p.pre.next = s;
- s.next = p;
- p.pre = s;
/**
*1:检查index是否越界
*2:如果index==size表示是插入到末尾结点
*3:否则,查找index结点node(index),将新的元素插入到index位置
*/
public void add(int index, E element) {
<!--检查index是否越界-->
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//接下来我们来看linkBefore方法:
/**
* Inserts element e before non-null Node succ.
*在指定非空结点的前面插入新的元素e
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//先取出原先该结点的前结点保存
final Node<E> pred = succ.prev;
//创建新的结点,新的节点的前结点即为原来succ.prev,后一个节点则为新的节点
final Node<E> newNode = new Node<>(pred, e, succ);
//scc的前结点变为新插入的结点newNode
succ.prev = newNode;
//pred==null 表示插入的为头结点
if (pred == null){
first = newNode;
}
else{
//将原来的头结点的next指针指向新的结点
pred.next = newNode;
}
size++;
modCount++;
}
复制代码
remove方法
双向链表的删除
- p.prior.next= p.next
- p.next.prior = p.prior
/**
* 1:检查index越界
* 2:取出index的结点,从链上删除
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
/**
*通过二分法查找
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);
//如果index小于当前size的一半,从前往后索引
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;
}
}
/**
移除一个非空节点 x
* 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 {
//前一个节点的next指向下一个结点
prev.next = next;
//方便GC回收
x.prev = null;
}
//next结点为空,表示删除的是一个尾结点
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
//gc
x.item = null;
size--;
modCount++;
//返回删除的结点元素
return element;
}
复制代码
removeFirst()删除头结点
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
/**
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
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;
}
复制代码
removeLast() 删除最后一个结点
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
/**
* Unlinks non-null last node 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;//尾指针指向prev
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
复制代码
get(int index)
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//看上去比较简单,但是注意node(index) 是一个for循环来遍历查找,索引的复杂度尾O(N)
复制代码
toArray()
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
复制代码
listIterator(int index)迭代器 分析
实现迭代器,在迭代器中实现删除,或查找,每次遍历后,都保留了上一次的结点,所以在实现查找,删除操作时,使得效率上得到提升。
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
复制代码
看一下ListItr
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned; //上次返回的结点
private Node<E> next; //下一个结点
private int nextIndex;//下一个结点的索引
private int expectedModCount = modCount;
/** 头指针的index
* @param index index of the first element to be returned from the
* list-iterator (by a call to {@code next})
*/
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index); //取出下一个结点给next
nextIndex = index;//第一次的索引即传进来的index
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
复制代码
从上面的分析可以看出,LinkedList在实现插入,删除元素时,时间的复杂度为O(1),要比ArrayList要快,但是,在查找元素时,要比ArrayList慢,时间复杂度为O(N),每次查询要遍历一遍。因此如果要求插入,删除比较快时,我们可以考虑用LinkedList,如:我们在实现一个历史对话列表,经常遇到置顶聊天,排序插入,删除等。在要求读取速度比较快时,我们可以考虑用Arraylist.
Other
发现一篇比较好博客 线性表之顺序表与单链表的区别及优缺点