//-----------------------------------------------------------
转载请注明出处:http://blog.csdn.net/chdjj
by Rowandjj
2014/8/8
//----------------------------------------------------------

注:下面源代码基于jdk1.7.0_11

上一篇我们分析了ArrayList,今天我们再来看下LinkedList。

首先上一幅框架图:

LinkedList相同间接继承了AbstractList抽象类,对外来看,LinkedList提供的操作接口跟ArrayList是非常类似的。区别在于内部实现上。略微有点基础的都知道,LinkedList是基于双向链表这样的数据结构,而ArrayList上一篇已经分析过了。是通过数组实现的。

我们依然依照之前的思路,自顶向下分析,AbstractList以及其上面的类或接口我们上一篇已经分析过,这里不再反复,我们从AbstractSequentialList開始。

package java.util;
public abstract class AbstractSequentialList<E> extends AbstractList<E> {
protected AbstractSequentialList() {//仅仅有一个构造器
}
public E get(int index) {//获取指定位置的值
try {
return listIterator(index).next();//通过迭代器的方式
} catch (NoSuchElementException exc) {//找不到就抛出异常
throw new IndexOutOfBoundsException("Index: "+index);//这是个Runtime异常
}
} public E set(int index, E element) {
try {
ListIterator<E> e = listIterator(index);//相同调用的listiterator
E oldVal = e.next();//记录
e.set(element);
return oldVal;//返回
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
public void add(int index, E element) {
try {
listIterator(index).add(element);
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
} public E remove(int index) {
try {
ListIterator<E> e = listIterator(index);
E outCast = e.next();
e.remove();
return outCast;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
// Bulk Operations
public boolean addAll(int index, Collection<? extends E> c) {
try {
boolean modified = false;
ListIterator<E> e1 = listIterator(index);
Iterator<? extends E> e2 = c.iterator();
while (e2.hasNext()) {
e1.add(e2.next());
modified = true;
}
return modified;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
// Iterators
public Iterator<E> iterator() {
return listIterator();
}
public abstract ListIterator<E> listIterator(int index);//參数为索引位置。表示从哪開始遍历
}
能够发现。这个抽象类中的方法都依赖于这个ListIterator迭代器,而这个获取迭代器的方法是抽象的,留给子类完毕。另外iterator方法并没有返回iterator,而相同是返回了listiterator对象。

接下来,我们分析LinkedList。

先看声明:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
须要注意的是LinkedList实现了Deque接口,这个接口代表一个双端队列,内部封装了双端队列的全部操作,故而LinkedList能够当做一个栈、队列或者是双端队列来使用
以下是其成员变量:
 transient int size = 0;//集合大小(结点个数)
transient Node<E> first;//头指针
transient Node<E> last;//尾指针

前面说过。linkedList是通过双向链表实现,故而不须要有扩容的方法,由于结点是动态申请的。

而这个结点的类型即为Node。以下看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;
}
}
非常显然是个双向链表的结点结构
再看LinkedList构造器:
 public LinkedList() {}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
再看一些对结点的操作方法:
假设你熟悉双向链表。就会发现以下几个函数非常easy,无非是处理指针的指向问题。
 private void linkFirst(E e) {//插到头部
final Node<E> f = first;
//创建一个新结点,前驱为空。后继为f(也就是当前的头结点)
final Node<E> newNode = new Node<>(null, e, f);//注意这样的泛型的写法也是能够的
first = newNode;//头指针指向新结点
if (f == null)//链表为空时
last = newNode;//尾指针指向新结点
else//否则
f.prev = newNode;//让f的前驱指向新结点
size++;
modCount++;
}
void linkLast(E e) {//插到尾部
final Node<E> l = last;//暂时变量记录尾结点
final Node<E> newNode = new Node<>(l, e, null);//创建新结点,前驱为l
last = newNode;//更新尾指针
if (l == null)//假设链表为空
first = newNode;
else
l.next = newNode;
size++;
modCount++;//用于高速失败机制
} void linkBefore(E e, Node<E> succ) {//将e插入succ之前
// assert succ != null;//调用者须要保证succ不为空
final Node<E> pred = succ.prev;//记录succ的前驱
final Node<E> newNode = new Node<>(pred, e, succ);//新结点的前驱指向succ的前驱,新结点的后继指向succ
succ.prev = newNode;//succ的前驱指向新结点
if (pred == null)//succ为头结点
first = newNode;//更改头指针
else
pred.next = newNode;//否则succ的前驱的后继指向新结点
size++;
modCount++;
}
private E unlinkFirst(Node<E> f) {//干掉头结点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;
} private E unlinkLast(Node<E> l) {//干掉尾结点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;
} E unlink(Node<E> x) {//干掉一个普通结点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;
}

有了这些基本函数之后。实现其它操作就方便了。比方这些:

 public void addFirst(E e) {
linkFirst(e);
}
public void addLast(E e) {
linkLast(e);
}
public boolean add(E e) {
linkLast(e);
return true;
}

再看这个remove方法:

 public boolean remove(Object o) {
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;
}
跟ArrayList类似,依据參数是否为null,进行了两种处理,说明LinkedList也是支持null的元素的

再看清空操作:
 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++;
}

以下的函数封装了索引链表位置的操作:

 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;
}
}
这个函数用了一个小技巧。首先推断待索引的位置是在链表前半部分还是后半部分,若是前半部分,则顺序索引。否则逆序索引(这就是双向链表的长处之中的一个)。

之前在AbstractSequentialList中未实现的方法在这里得到了实现:
 public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}

这个ListItr是LinkedList的内部类,实现了ListIterator接口。

private class ListItr implements ListIterator<E>
private Node<E> next;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
通过构造器指定起始遍历的位置,内部通过调用node方法索引该位置的对象。详细方法限于篇幅不在介绍。
值得一提的是这个类还提供了一个反向的迭代器:
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
这个反向迭代器事实上是对上面介绍的ListItr的封装。


总结:
1.LinkedList内部通过双向链表实现;
2.LinkedList支持null元素;
3.LinkedList插入删除元素较方便。可是查找操作较耗时(对照ArrayList)。尽管内部进行了优化(依据位置选择顺序还是逆序遍历);
4.LinkedList内部相同通过内部类的形式实现了迭代器(仅实现了ListIterator,iterator方法返回的也是ListIterator对象)。
5.LinkedList实现了Deque接口,能够当成栈、队列、双端队列来使用。

版权声明:本文博客原创文章。博客,未经同意,不得转载。

【源代码】LinkedList源代码分析的更多相关文章

  1. Java中arraylist和linkedlist源代码分析与性能比較

    Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arra ...

  2. es6-promise源代码重点难点分析

    摘要 vue和axios都可以使用es6-promise来实现f1().then(f2).then(f3)这样的连写形式,es6-promise其实现代浏览器已经支持,无需加载外部文件.由于promi ...

  3. AXIOS源代码重点难点分析

    摘要 vue使用axios进行http通讯,类似jquery/ajax的作用,类似angular http的作用,axios功能强大,使用方便,是一个优秀的http软件,本文旨在分享axios源代码重 ...

  4. 【Java集合源代码剖析】LinkedList源代码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/35787253 您好.我正在參加CSDN博文大赛.假设您喜欢我的文章,希望您能帮我投一票,谢 ...

  5. JDK7 LinkedList源代码分析

    transient int size = 0; /** * Pointer to first node. * Invariant: (first == null && last == ...

  6. x264源代码 概述 框架分析 架构分析

    函数背景色 函数在图中以方框的形式表现出来.不同的背景色标志了该函数不同的作用: 白色背景的函数:不加区分的普通内部函数. 浅红背景的函数:libx264类库的接口函数(API). 粉红色背景函数:滤 ...

  7. 从源代码的角度分析--在BaseAdapter调用notifyDataSetChanged()之后发生了什么

    导师安排我做一个小项目,其中涉及到利用Adapter作为ListView的适配器,为ListView提供数据.选中某一项后,要让这一项变成选中状态,也就是背景图片要换一下.下面我就用一个小例子来模拟. ...

  8. 【第四篇】androidEventbus源代码阅读和分析

    1,分析androidEventbus的注册源代码: 我们在使用androidEventbus的第一步是注册eventbus,如下代码: EventBus.getDefault().register( ...

  9. vue 2.0 路由切换以及组件缓存源代码重点难点分析

    摘要 关于vue 2.0源代码分析,已经有不少文档分析功能代码段比如watcher,history,vnode等,但没有一个是分析重点难点的,没有一个是分析大命题的,比如执行router.push之后 ...

随机推荐

  1. HorizontalScrollView做页卡的一个小记录

    用HorizontalScrollView做页卡,实现一个如下图的效果:

  2. Linux cp -a用法

    对于cp -a最主要的用法是在保留原文件属性的前提下复制文件.其实还有个很好的用法,如下: 大家知道linux下复制目录可以通过,cp -r dirname destdir 但是这样复制的目录属性会发 ...

  3. Linux日志清除

    因为数据要求.经常需要抓住和筛选过滤数据,大概花了7 8个月.改变了机旁数据.重新开始,发现"No space left on device" 解决方法: 直接删除日志(简单粗暴) ...

  4. 【 D3.js 入门系列 --- 4 】 怎样使用scale(比例)

    本人的个人博客为: www.ourd3js.com csdn博客为: blog.csdn.net/lzhlzz 转载请注明出处,谢谢. 在上一节中使用了一个非常重要的概念 - scale (这个不知道 ...

  5. 重新想象 Windows 8 Store Apps (29) - 图片处理

    原文:重新想象 Windows 8 Store Apps (29) - 图片处理 [源码下载] 重新想象 Windows 8 Store Apps (29) - 图片处理 作者:webabcd介绍重新 ...

  6. 使用 WPF 实现所见即所得HTML编辑器

    Introduction In this tip, you will learn the use of WPF webbrowser control and the use of the librar ...

  7. CSDN 夏令营课程 项目分析

    主题如以下: 正确改动后的程序: #include <iostream.h> //using namespace std; class BASE { char c; public: BAS ...

  8. 全新E:网站不是之前排名浮动 相比于竞争对手究竟缺少了什么?

    这几天有非常多朋友问新辰,为什么站点排名掉了?为什么被人家逆袭反超了?当然,这无疑与你站点的内容.外链和用户体验有非常大关系,只是.新辰在此觉得,还须要多研究一下竞争对手的站点,做到:人无我有.人有我 ...

  9. 在线maven 仓库

    findmaven.net是一个查找Jar和查找Maven的Maven仓库搜索引擎.它能够依据Java开发人员提供的Class名或者Jar名找到包括它的Jar,同一时候提供Jar的Maven仓库链接, ...

  10. [LeetCode238]Product of Array Except Self

    题目: Given an array of n integers where n > 1, nums, return an array output such that output[i] is ...