jdk源码分析之LinkedList
2016-05-23 15:06
651 查看
LinkedList关键属性
size表示当前链表保存了多少数据,first指针指向链表第一个数据,last指针指向链表最后一个数据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;
LinkedList底层数据结构
Linked是一个双向链表,链表节点的数据如下,有一个数据域和两个指针域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首尾添加数据linkFirst(E e)和linkLast(E e)
/** * 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++; }
先保存旧链表的尾指针为l,然后根据传入的数据e和为指针l构建一个数据节点。
newNode = new Node<>(l, e, null)
将链表的尾节点修改为新创建的节点newNode。如果l==null,表示之前链表里边没有保存数据,现在添加了一个新数据,头指针和尾指针都应该指向这个新添加的节点
last = newNode; if (l == null) first = newNode;
如果之前链表里边保存的有数据,l!=null,则修改新节点链接到原链表的尾部
l.next = newNode;
然后修改size和modCount(迭代器并发访问用到的属性)
linkFirst(E e)代码类似,原理一样
在LinkedList的某一节点前插入数据linkBefore
/** * 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++; }
在节点succ前插入数据 e,则succ是e的后继节点,e的前序就是succ的前序,因此根据e、前序、后继构造一个新的节点newNode = new Node<>(pred, e, succ),修改succ的前序节点为新节点newNode,succ.prev = newNode,然后再将断开的链表链接起来
LinkedList删除头结点unlinkFirst和尾节点unlinkLast
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; }
首先保存头结点的后继节点指针(即将成为新的头结点),然后头结点指针域和数据域置null,加快内存回收速度,然后判断next是否为null,null的话表示原链表只有一个节点,现在还把唯一的节点删除了,因此first头指针和last尾指针都应该为null。next不为null的话表示删除头结点后链表还有数据,修改next的prev指针为null即可(pre指针为null表示此接节点为队首数据,next指针为null表示队尾数据)
unlinkLast(Node l) 代码和原理类似
LinkedList删除某一节点unlink
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 { 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; }
仍然是指针操作,提取待删除节点的数据域、前序和后继,数据域用于返回值,前序和后继用于链表的断连操作
前序为null表示带删除节点是队首节点,修改first指针为待删除节点的后续节点
前序节点不为null就把前序节点的next指针指向后继节点,待删除节点的prev指针域置null,这样待删除节点的前序节点指针已经断开重连了,然后处理后继节点指针的断开重连
判断后继节点是否为null,是null的话表示待删除节点就是队尾,修改队尾指针last为待删除节点的前序
后继节点不为null,后继节点的prev指针指向待删除节点的前序节点,待删除节点的next指针置null,这样待删除节点的后继结点的指针也已经断开重连了
然后把待删除节点的数据域置null,修改size和modCount,返回删除节点的数据
LinkedList的随机访问node(int index)
/** * Returns the (non-null) Node at the specified element index. */ 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; } }
链表插入删除添加数据比较方便,直接修改指针的断连即可。但是链表并不能够像数组那样随机访问,只能进行遍历。LinkedList是一个双向链表,可以对遍历做一个小优化,只遍历半个链表即可。
如果位置在链表前半部分index < (size >> 1),正向遍历查找for (int i = 0; i < index; i++)
否则逆向遍历查找for (int i = size - 1; i > index; i–)
LinkedList的某一位置添加一个集合addAll
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) { succ = null; pred = last; } else { succ = node(index); pred = succ.prev; } for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } if (succ == null) { last = pred; } else { pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true; }
首先检查插入位置是否合法
private void checkPositionIndex(int index) { if (!isPositionIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
private boolean isPositionIndex(int index) { return index >= 0 && index <= size; }
可见插入位置区间为【0,size】,可以队首插入,可以队尾插入,中间任意位置当然也可以
然后把集合对象转化为数组,数组长度为0表示集合没有数据,直接返回false表示插入失败
构建插入节点的前序和后继,如果插入位置index==size的话表示队尾插入数据,因此后续为null,前序为队尾last
Node<E> pred, succ; if (index == size) { succ = null; pred = last; }
不是在队尾插入集合的话需要根据插入位置index找到后继节点,找到了后继也就找到了前序
else { succ = node(index); pred = succ.prev; }
Node node(int index)方法用于返回特定位置的节点,这样插入点的前序和后继都找到了,开始循环把集合的数据添加到链表的特定位置
for (Object o : a) { E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; }
通过new Node<>(pred, e, null)创建新节点,前序指针在创建的时候在构造函数里边已经指明。
pred == null表示是在队首添加数据,需要修改队首指针first
否则把前序节点的后继指针设置为newNode
再把前序节点设为newNode,接着下一次循环
这样在循环结束后,只有最后一个添加的节点的后继指针没有处理
if (succ == null) { last = pred; } else { pred.next = succ; succ.prev = pred; }
如果succ后继为null,修改队尾指针为last为最后一个添加的节点pred
否则设置最有一个添加的节点的后继指针为succ,succ的前序指针指向最后一个添加的节点pred
最后修改size和modCount,返回true表示添加成功
LinkedList的双端队列特性
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList实现了Deque接口,因此可以吧LinkedList当做一个双端队列属性,比如peek和poll,均是通过链表的基本操作实现相应的功能,其他的方法类似,不再一一列举。
public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); }
相关文章推荐
- 从源码安装Mysql/Percona 5.5
- JDK动态代理VS CgLib
- Ubuntu 安装 JDK 问题
- 浅析Ruby的源代码布局及其编程风格
- asp.net 抓取网页源码三种实现方法
- JS小游戏之仙剑翻牌源码详解
- JS小游戏之宇宙战机源码详解
- jQuery源码分析之jQuery中的循环技巧详解
- 本人自用的global.js库源码分享
- java中原码、反码与补码的问题分析
- ASP.NET使用HttpWebRequest读取远程网页源代码
- jdk与jre的区别 很形象,很清晰,通俗易懂
- jdk中String类设计成final的原由
- win7下安装 JDK 基本流程
- jdk环境变量配置
- win2003 jsp运行环境架设心得(jdk+tomcat)
- windows linux jdk安装配置方法
- Java编程之jdk1.4,jdk1.5和jdk1.6的区别分析(经典)
- 详解JDK 5 Annotation 注解之@Target的用法介绍
- PHP网页游戏学习之Xnova(ogame)源码解读(六)