Java源码 -- LinkedList
1.1、LinkedList概述
LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的。
LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
LinkedList 是非同步的。
1.2、LinkedList的数据结构
1)基础知识补充
1.1)单向链表:
element:用来存放元素
next:用来指向下一个节点元素
通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。
1.2)单向循环链表
element、next 跟前面一样
在单向链表的最后一个节点的next会指向头节点,而不是指向null,这样存成一个环
1.3)双向链表
element:存放元素
pre:用来指向前一个元素
next:指向后一个元素
双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。
1.4)双向循环链表
element、pre、next 跟前面的一样
第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。
2)LinkedList的数据结构
如上图所示,LinkedList底层使用的双向链表结构,有一个头结点和一个尾结点,双向链表意味着我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作。
1.3、LinkedList的特性
在我们平常中我们只知道一些常识性的特点:
1)是通过链表实现的,
2)如果在频繁的插入,或者删除数据时,就用linkedList性能会更好。
那我们通过API去查看它的一些特性
1)Doubly-linked list implementation of the List
and Deque
interfaces. Implements all optional list operations, and permits all elements (including null
).
这告诉我们,linkedList是一个双向链表,并且实现了List和Deque接口中所有的列表操作,并且能存储任何元素,包括null,
这里我们可以知道linkedList除了可以当链表使用,还可以当作队列使用,并能进行相应的操作。
2)All of the operations perform as could be expected for a doubly-linked list. Operations that index into the list will traverse the list from the beginning or the end, whichever is closer to the specified index.
这个告诉我们,linkedList在执行任何操作的时候,都必须先遍历此列表来靠近通过index查找我们所需要的的值。通俗点讲,这就告诉了我们这个是顺序存取,
每次操作必须先按开始到结束的顺序遍历,随机存取,就是arrayList,能够通过index。随便访问其中的任意位置的数据,这就是随机列表的意思。
3)api中接下来讲的一大堆,就是说明linkedList是一个非线程安全的(异步),其中在操作Interator时,如果改变列表结构(add\delete等),会发生fail-fast。
通过API再次总结一下LinkedList的特性:
1)异步,也就是非线程安全
2)双向链表。由于实现了list和Deque接口,能够当作队列来使用。
链表:查询效率不高,但是插入和删除这种操作性能好。
3)是顺序存取结构(注意和随机存取结构两个概念搞清楚)
1.4、源码分析
属性,构造器
//实际元素个数
transient int size = 0; //指向第一个节点的指针。 transient Node<E> first; //指向第一个节点的指针 transient Node<E> last; //无参构造器 public LinkedList() { }
//有参构造器
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
内部类
//linkedList的奥秘就在这里,通过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; } }
核心方法
linkFirst
//链接e作为第一个元素。就是让当前e元素作为链表的第一个元素 private void linkFirst(E e) { final Node<E> f = first; //先将第一个节点赋值给新节点 final Node<E> newNode = new Node<>(null, e, f); //将e元素传入,创建一个新节点,f节点作为后继 first = newNode; //把e节点赋值给第一个节点 if (f == null) //如果第一个节点是null,则newNode 节点即是第一个也是最后一个节点 last = newNode; else //如果不为空,则将f节点的前驱指向第一个节点newNode f.prev = newNode; size++; //大小自增 modCount++; //当修改时,modCount自增,防止并发异常,fail-fast }
linkLast
//没有作用域,默认是friendly,范围是在同一个类,同一个package中
//链接e作为最后一个元素。与上面相反,将当前e元素作为链表的最后一个元素 void linkLast(E e) { final Node<E> l = last; //先将最后一个节点赋值给新节点 final Node<E> newNode = new Node<>(l, e, null); //将e元素传入,创建一个新节点,l节点作为前驱节点 last = newNode; //把新节点赋值给最后一个节点 if (l == null) //如果最后的节点是null,则newNode 节点即是第一个也是最后一个节点 first = newNode; else //如果不为空,l节点的后继指向最后一个节点 l.next = newNode; size++; //大小自增 modCount++; //modCount自增 }
linkBefore
//在非空节点succ之前插入元素e void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; //将succ节点的前驱节点赋值一个新节点 final Node<E> newNode = new Node<>(pred, e, succ); //创建一个新节点,因为在succ之前,所以newNode的后继节点是succ,前驱节点是succ之前所指向的节点 succ.prev = newNode; //此时succ前驱节点就是新节点newNode if (pred == null) //如果succ前驱节点是null,则succ是链表的第一个节点,所以newNode将是第一个节点 first = newNode; else //如果不为null,则succ的前驱节点的后继节点即是新插入的newNode节点 pred.next = newNode; size++; //大小自增 modCount++; //modCount自增 }
unlinkFirst
//取消非空的第一个节点f的链接。这个意思是 移除链表的第一节点 private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; //将节点元素内容拿出来 final Node<E> next = f.next; //将f节点的后继节点拿出来 f.item = null; f.next = null; // help GC 将其清空,呼叫垃圾回收人员来干活了 first = next; //让f节点的后继节点成为第一个节点 if (next == null) //如果next节点为null,则f节点就是最后一个节点 last = null; else //如果不为空,成为第一个节点的前驱节点即是null next.prev = null; size--; //大小自减 modCount++; //modCount自增 return element; //返回删除节点元素内容 }
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的前驱节点赋值一个新节点prev l.item = null; l.prev = null; // help GC 呼叫垃圾回收,请求帮助 last = prev; //删除最后一个节点l后,l的前驱节点prev便是最后一个节点 if (prev == null) //若prev节点为空,则l是链表的唯一节点,所以删除当前节点,链表清空,first为null first = null; else //如果不为空,则prev下一个节点为null prev.next = null; size--; modCount++; return element; }
unlink
//删除链表中的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) { //如果前驱节点prev为null,则x是链表的第一节点,删除后后继节点next便是第一个节点 first = next; } else { //如果prev不为空,删除x节点后,x的前驱节点prev的后继节点就是x的后继节点next。(1、2、3 少了2, 那1后面的就是3啦) prev.next = next; x.prev = null; } if (next == null) { //如果后继节点next为null,则x是链表的最后一个节点,删除后后继节点prev便是最后的节点 last = prev; } else { //如果next不为空,删除x节点后,x的后继节点next的前驱节点就是x的前驱节点prev。(1、2、3 少了2, 那3前面的就是1啦) next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
增删改查方法
//返回列表中的第一个元素 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; } //删除链表的第一个元素 public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); //调用unlinkFirst方法删除 } //删除链表的最后一个元素 public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); //调用unLinkList方法删除 } //将元素添加到列表头部 public void addFirst(E e) { linkFirst(e);//调用linkFirst方法添加 } //将元素添加到列表尾部 public void addLast(E e) { linkLast(e); //调用linkLast方法添加 } //默认将元素添加到尾部 public boolean add(E e) { linkLast(e); return true; } //删除节点 public boolean remove(Object o) { if (o == null) { //如果o为空,则找出列表为空的删除 for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); //调用unlink删除 return true; } } } else { //如果o不为空,则找出删除 for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); //调用unlink删除 return true; } } } return false; } //添加集合 public boolean addAll(Collection<? extends E> c) { return addAll(size, c);//默认是添加链表最后 } // public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index); //越界检查 Object[] a = c.toArray(); //将内容转换为数组 int numNew = a.length; //长度 if (numNew == 0) //为空便什么不用干了 return false; Node<E> pred, succ; //创建两个空节点 if (index == size) { //如果index==size 则在链表最后添加元素 succ = null; pred = last; } else { //如果index!=size succ = node(index); //这个是获得index位置的节点 pred = succ.prev; //index位置节点的前置节点 } for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); //循环数组,创建新节点 if (pred == null) //如果index位置节点的前置节点prev是null,即index是首位 first = newNode; else pred.next = newNode; //每一次循环的节点 是 上一节点的后继节点 pred = newNode; //更新prev节点 } if (succ == null) { //如果index位置的节点为null,则pred为最后的节点 last = pred; } else { //将节点连接上,index位置节点的前置节点便是循环添加的最后一个节点,最后一个节点prev的后继节点便是succ节点 pred.next = succ; succ.prev = pred; } size += numNew; //大小更新 modCount++; return true; } //清空链表内容 public void clear() { for (Node<E> x = first; x != null; ) { //循环清空后,呼叫GC Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; } //位置访问操作 //获取指定位置的内容 public E get(int index) { checkElementIndex(index); //越界检查 return node(index).item; //调用对应方法 } //设置指定位置节点内容 public E set(int index, E element) { checkElementIndex(index); //越界检查 Node<E> x = node(index); //获取位置节点 E oldVal = x.item; x.item = element; return oldVal; //返回老节点内容 } //添加节点 public void add(int index, E element) { checkPositionIndex(index); //越界检查 //根据不同情况,选择不同添加方式:1 位置添加;2 最后位置添加 if (index == size) linkLast(element); else linkBefore(element, node(index)); } //指定位置删除 public E remove(int index) { checkElementIndex(index);//越界检查 return unlink(node(index)); //调用删除方法 }
//返回指定元素索引处的(非空)节点。
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { //index在链表前面区域 size >> 1 等同于 size除以2
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { //index在链表后面区域
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
搜索操作的方法
//找出元素第一次的位置 public int indexOf(Object o) { int index = 0; //初始位置0 if (o == null) { //元素为空。找出为空节点的位置 for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; //每次循环,位置自增 } } else { //元素不为空。找出元素位置 for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; //每次循环,位置自增 } } return -1; } //找出元素最后一次出现的位置 public int lastIndexOf(Object o) { int index = size; //初始位置为size,最后的位置 if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { for (Node<E> x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; } } return -1; }
队列操作的方法
//返回链表第一个节点内容
final Node<E> f = first; return (f == null) ? null : f.item; } //返回链表第一个节点内容 public E element() { return getFirst(); } //删除链表第一个节点,并返回 public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } //删除第一个节点,并返回 public E remove() { return removeFirst(); } //在首位,添加节点 public boolean offer(E e) { return add(e); } // 双端队列的操作
//在首位 添加节点 public boolean offerFirst(E e) { addFirst(e); return true; } //在尾部,添加节点 public boolean offerLast(E e) { addLast(e); return true; } //返回第一个节点中的元素 public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; } //返回最后一个节点中的元素 public E peekLast() { final Node<E> l = last; return (l == null) ? null : l.item; } //删除第一个节点内容,并返回 public E pollFirst() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } //删除最后一个节点内容,并返回 public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l); } //在首部,添加节点 public void push(E e) { addFirst(e); } //删除第一个节点 public E pop() { return removeFirst(); } //删除此列表中指定元素的第一个匹配项(在从头到尾遍历列表时) public boolean removeFirstOccurrence(Object o) { return remove(o); } //删除此列表中指定元素的最后一次出现(从首尾遍历该列表时)。 public boolean removeLastOccurrence(Object o) { if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { if (x.item == null) { unlink(x); //调用删除方法 return true; } } } else { for (Node<E> x = last; x != null; x = x.prev) { if (o.equals(x.item)) { unlink(x); //调用删除方法 return true; } } } return false; }
LinkedList的迭代器
在LinkedList中除了有一个Node的内部类外,应该还能看到另外两个内部类,那就是ListItr,还有一个是DescendingIterator。
1、ListItr内部类
只继承了一个ListIterator
看到方法名之后,就发现不止有向后迭代的方法,还有向前迭代的方法,所以我们就知道了这个ListItr这个内部类干嘛用的了,就是能让linkedList不光能像后迭代,也能向前迭代。
看一下ListItr中的方法,可以发现,在迭代的过程中,还能移除、修改、添加值得操作。
2 DescendingIterator内部类
//看一下这个类,还是调用的ListItr,作用是封装一下Itr中几个方法,让使用者以正常的思维去写代码,例如,在从后往前遍历的时候,也是跟从前往后遍历一样,使用next等操作,而不用使用特殊的previous。 private class DescendingIterator implements Iterator<E> { private final ListItr itr = new ListItr(size()); public boolean hasNext() { return itr.hasPrevious(); } public E next() { return itr.previous(); } public void remove() { itr.remove(); } }
总结
1)linkedList本质上是一个双向链表,通过一个Node内部类实现的这种链表结构。
2)能存储null值
3)跟arrayList相比较,就真正的知道了,LinkedList在删除和增加等操作上性能好,而ArrayList在查询的性能上好
4)从源码中看,它不存在容量不足的情况
5)linkedList不光能够向前迭代,还能像后迭代,并且在迭代的过程中,可以修改值、添加值、还能移除值。
6)linkedList不光能当链表,还能当队列使用,这个就是因为实现了Deque接口。