LinkedList简介
LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈、队列和双端队列来使用。
LinkedList同样是非线程安全的,只在单线程下适合使用。
LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。
LinkedList源码
以下是linkedList源码(加入简单代码注释):
LinkedList详细分析
一、什么是链表
链表是由一系列非连续的节点组成的存储结构,简单分下类的话,链表又分为单向链表和双向链表,而单向/双向链表又可以分为循环链表和非循环链表,下面简单就这四种链表进行图解说明。
1.单向链表
单向链表就是通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。
2.单向循环链表
单向循环链表和单向列表的不同是,最后一个节点的next不是指向null,而是指向head节点,形成一个“环”。
3.双向链表
从名字就可以看出,双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。
4.双向循环链表
双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。而LinkedList就是基于双向循环链表设计的。
二、定义
看一下LinkedList的定义部分:
1
2
3
|
public
class
LinkedList<E>
extends
AbstractSequentialList<E>
implements
List<E>, Deque<E>, Cloneable, java.io.Serializable
|
可以看出LinkedList 继承AbstractSequentialList 抽象类,实现了List,Deque,Cloneable,Serializable 几个接口,AbstractSequentialList 继承 AbstractList,是对其中方法的再抽象,其主要作用是最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作,简单说就是,如果需要快速的添加删除数据等,用AbstractSequentialList抽象类,若是需要快速随机的访问数据等用AbstractList抽象类。
Deque 是一个双向队列,也就是既可以先入先出,又可以先入后出,再直白一点就是既可以在头部添加元素又在尾部添加元素,既可以在头部获取元素又可以在尾部获取元素。看下Deque的源码(已删除注释):
三、内部类
LinkedList总共有三个内部类,ListItr,Entry<E>和DescendingIterator,这三个内部类分别实现了List迭代器,双向链表的节点所对应的数据结构和反向迭代器。
3.1内部类ListItr
源码在上面的代码中已经给出,这里不再给出,分析一下
3.1.1构造函数
在这里,这个构造函数只做了一件事情,就是加速查找,重点是这句话if (index < 0 || index > size)如果给出的index索引小于双向链表长度的一半,则从头向后查找,如果大于,则从尾向前查找。
3.1.2成员变量
这个内部类当中的其他方法这里就不介绍了,set(),get(),add()等等这些方法,在最开始的注释代码中写的很清晰了,这里说其中的一个成员变量private int expectedModCount = modCount;
首先来看一下这个modCount是个什么东西:它是LinkedList继承的父类的父类AbstractList<E>中的一个成员变量protected transient int modCount = 0;它是用来记录AbstractList结构性变化的次数。http://item.taobao.com/item.htm?id=41222768202
在AbstractList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。
而在这里ListItr自己同时维护了一个expectedModCount,初始值是取的modCount,而当ListItr结构性变化的时候expectedModCount也会自动修改,包括next(),previous(),remove(),add(E e)等方法,而同样在这些方法里面的第一步都会去调用checkForComodification()这个方法进行修改的同步检查,如果不同步将会抛出ConcurrentModificationException()这个异常。
3.2内部类Entry<E>
3.2.1构造方法
这个构造函数使用到了三个参数,分别是当前节点值,下一节点和上一节点。
3.2.2addBefore(E e, Entry<E> entry)
在这个内部类中,实现了将将节点e添加到entry节点之前的这个方法,这个方法在LinkedList所有添加操作相关的方法中都调用了,源码:
在这个方法一开始的地方就new了一个新的Entry对象出来,经典的双向列表固定位置添加新节点,并且更新前驱结点的next域和后驱结点的previous域,并且将记录操作数的modCount自增1。http://item.taobao.com/item.htm?id=41222768202
3.3内部类DescendingIterator
反向迭代器实现类,这个类比较简单,直接调用的ListItr的方法,在此不再赘述。
四、增加
第一个add在尾部增加元素比较好理解:在header前添加元素e,header前就是最后一个结点,就是在最后一个结点的后面添加元素e;而第二个增加就不是那么简单了,同样都是调用Entry的addBefore方法,第二个方法增加了一句判断条件index==size ? header : entry(index),这个条件实际上的意思就是如果index等于list元素个数,则在队尾添加元素(header之前),否则在index节点前添加元素。
到这里可以发现一点,header作为双向循环链表的头结点是不保存数据的,也就是说hedaer中的element永远等于null。
第一个addAll方法是调用了第二个,仔细研究一下第二个addAll的方法,首先第一句话是越界检查,第二局判断是对添加的Collection做非0校验,Entry<E> successor = (index==size ? header : entry(index));这句话的含义是:获取要插入index位置的下一个节点,如果index正好是lsit尾部的位置那么下一个节点就是header,否则需要查找index位置的节点,而Entry<E> predecessor = successor.previous;这句话的含义是:获取要插入index位置的上一个节点,因为是插入,所以上一个点击就是未插入前下一个节点的上一个。
五、删除、修改、查询
5.1删除
在这里删除本质上的意思就是前一节点和后一节点组合在一起就好了,互相修改一下前驱结点和后置节点
5.2修改
set方法看起来简单了很多,只要修改该节点上的元素就好了。
5.3查找
LinkedList是通过从header开始index计为0,然后一直往下遍历(next),直到到底index位置。为了优化查询效率,LinkedList采用了二分查找(这里说的二分只是简单的一次二分),判断index与size中间位置的距离,采取从header向后还是向前查找。
基于双向循环链表实现的LinkedList,通过索引Index的操作时低效的,index所对应的元素越靠近中间所费时间越长。而向链表两端插入和删除元素则是非常高效的(如果不是两端的话,都需要对链表进行遍历查找)。
六、是否包含
indexOf查询元素位于容器的索引位置,都是需要对链表进行遍历操作,也都是低效的操作,慎用。
七、LinkedList实现的双端队列
很简单,逻辑都是基于上面讲的链表操作的。