美文网首页javase文章收集JDK源码
java.util.LinkedList源码解析

java.util.LinkedList源码解析

作者: sunpy | 来源:发表于2017-06-20 22:38 被阅读37次

java集合框架图

20160918105654_491.gif

所属包

package java.util; 

继承与实现关系

    public class LinkedList<E>  
        extends AbstractSequentialList<E>  
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable  

准备知识

结构图:

LinkedList.png

①LinkedList是一种双向链表。
②根据双向链表的特点,会有头节点和尾节点,并且节点之间是通过前驱指针和后继指针来维护关系的,而不是像数组那样通过位置下标来维护节点间关系的。所以既可以从头到尾遍历,又可以从尾到头遍历。

LinkedList中Node节点类的数据结构

//E类型的Node类,作为LinkedList双向链表中的节点  
private static class Node<E> {  
      //E类型的值item  
      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;  
      }  
}  

属性

//初始化链表的长度为0  
transient int size = 0;  
      
//指向头节点的变量first   
transient Node<E> first;  
      
//指向尾节点的变量last   
 transient Node<E> last;  

构造方法

构造方法1:

//构造一个空的LinkedList链表结构  
public LinkedList() {  

}  

构造方法2:

//构造一个包含指定元素的collection集合中元素的LinkedList   
public LinkedList(Collection<? extends E> c) {  
     this();  
     //使用addAll方法,实际上就是使用遍历c并且采用头插法进行双向链表插入值。  
     addAll(c);  
}  

方法

addAll方法将指定集合中的所有元素从指定位置开始插入此列表:

public boolean addAll(Collection<? extends E> c) {  
        return addAll(size, c);  
} 

追加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。

public boolean addAll(int index, Collection<? extends E> c) {  
     //判断下标元素是否在链表的长度范围之内  
     checkPositionIndex(index);  
     //将集合c转换成Object数组  
     Object[] a = c.toArray();  
     int numNew = a.length;  
     //如果Object数组长度为0,那么就返回添加失败  
     if (numNew == 0)  
         return false;  
     //pred节点为succ节点的前驱  
     Node<E> pred, succ;  
     //如果下标等于链表的长度时,pred为尾节点,succ指向为空      
     if (index == size) {
           succ = null;  
           pred = last;  
     } else {
           /*  如果下标不等于链表长度,node方法就是用来通过下标索引获
            *  取链表中的对应的节点对象。  
            */
           succ = node(index);  
           pred = succ.prev;  
     }  
     for (Object o : a) {  
          @SuppressWarnings("unchecked") 
          E e = (E) o;  
          //将遍历的值包装成节点Node,初始化前驱节点为pred,后继节点为null  
          Node<E> newNode = new Node<>(pred, e, null);  
          //如果前驱节点为空,那么肯定是头节点  
          if (pred == null)  
                first = newNode;  
          else
          //否则不是头节点,那么前驱的后继节点为当前节点,其实就是类似于链表的插入节点操作。  
                pred.next = newNode;  
                pred = newNode;  
     }  
     /*
      * 因为pred节点是succ节点的前驱节点,反之succ是pred的后继节点.
      *  如果succ为空,说明pred为尾节点。  
      */
     if (succ == null) {  
          last = pred;  
     } else {  
           /* 如果succ不是尾节点,那么只要保证pred节点是succ节点的前驱 
            * 节点、succ是pred的后继节点这种双向链表的关系 
            */ 
           pred.next = succ;  
           succ.prev = pred;  
     }  
     size += numNew;  
     modCount++;  
     return true;  
}  

add(E e)方法将元素插入到链表的尾部:

//采用双向链表的尾插法
public boolean add(E e) {  
      linkLast(e);  
      return true;  
}  
void linkLast(E e) {  
    //创建临时节点l初始化为尾节点(那么其后继节点为null,前驱节点不为空)。  
    final Node<E> l = last;  
    //初始化新节点,前驱节点为l,后继暂为null  
    final Node<E> newNode = new Node<>(l, e, null);  
    //由于是在链表尾部插入节点,那么新节点就作为尾节点。  
    last = newNode;  
    /**  
     *  l节点作为newNode节点的前驱节点。  
     *  如果l为空,那么newNode前驱节点为空。  
     *  在双向链表中,前驱节点为空,那么该节点为头节点。  
     */  
     if (l == null)  
            first = newNode;  
     else  
            l.next = newNode;  
     size++;//再插入节点后,链表的长度加1  
     modCount++;  
} 

add(int index, E element)方法:

public void add(int index, E element) {  
     //先检查索引是否在链表的范围内。  
     checkPositionIndex(index);  
      
     if (index == size)  
           //如果索引等于链表长度,那么直接采用尾插法的linkLast方法。  
           linkLast(element);  
     else  
           linkBefore(element, node(index));  
}  
/**  
   * 在节点succ作为通过下标索引在链表中查询出来的对应的节点。  
   * e值包装的节点插入到succ节点之前。  
   */  
void linkBefore(E e, Node<E> succ) {  
    //succ节点的前驱为pred  
    final Node<E> pred = succ.prev;  
    //初始化新节点前驱为pred,后继为succ,意思就是想在pred和succ节点之间插入newNode节点  
    final Node<E> newNode = new Node<>(pred, e, succ);  
    //到这步,newNode已经确立了,后继节点为succ。succ节点的前驱为newNode。  
    succ.prev = newNode;  
    //如果pred为空,那么newNode的前驱节点为空,可以确定newNode为头节点。
    if (pred == null)  
        first = newNode;  
    else
        /* 如果pred不为空,则确定了pred节点后继为newNode,之前已经确
         * 定newNode的前驱为pred,这样pred和newNode就确立关系了。
         */  
        pred.next = newNode;  
    size++;//新增节点,长度更新为原来长度加1  
    modCount++;  
}  

addFirst方法将指定元素插入到链表的头部

   /**
     * 将指定元素插入到链表的头部
     */
    public void addFirst(E e) {
        linkFirst(e);
    }
   /**
     * 将e作为头结点,采用双向链表的头插法
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        //在头结点前面插入元素,该元素就作为头结点了
        first = newNode;
        //如果原头结点为空,那么新节点即是头结点也是尾节点
        if (f == null)
            last = newNode;
        else
        //如果原头结点不为空,那么新节点插入到原头结点前面
            f.prev = newNode;
        size++;
        modCount++;
    }

addLast方法插入元素到链表的尾部:

   /**
     * 将指定元素插入到链表的尾部
     */
    public void addLast(E e) {
        linkLast(e);
    }
//采用双向链表尾插法的方式插入元素
void linkLast(E e) {  
    //创建临时节点l初始化为尾节点(那么其后继节点为null,前驱节点不为空)。  
    final Node<E> l = last;  
    //初始化新节点,前驱节点为l,后继暂为null  
    final Node<E> newNode = new Node<>(l, e, null);  
    //由于是在链表尾部插入节点,那么新节点就作为尾节点。  
    last = newNode;  
    /**  
     *  l节点作为newNode节点的前驱节点。  
     *  如果l为空,那么newNode前驱节点为空。  
     *  在双向链表中,前驱节点为空,那么该节点为头节点。  
     */  
     if (l == null)  
            first = newNode;  
     else  
            l.next = newNode;  
     size++;//再插入节点后,链表的长度加1  
     modCount++;  
} 

peek方法获取列表的头元素,但是不移除列表的头

   /**
     * 获取列表的头元素,但是不移除列表的头
     */
    public E peek() {
        final Node<E> f = first;
        //如果头结点为空,则返回空,否则返回头结点的值
        return (f == null) ? null : f.item;
    }
   /**
     * 删除原头结点,将头结点的后继结点作为头结点
     */
    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;
    }

poll方法获取列表的头节点元素,并且还移除该头结点元素:

   /**
     * 获取列表的头节点元素,并且还移除该头结点元素
     */
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : 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.item = null;
        f.next = null; // help GC
        //将后继结点作为头结点
        first = next;
        //如果后继结点为空,那么就将尾节点设置为空
        if (next == null)
            last = null;
        else
        //如果后继结点不为空,那么作为头结点,就将前驱关系解除,设置为空。
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

remove(Object o)方法,删除列表中移除首次出现的元素

   /**
     * 列表中移除首次出现的元素
     */
    public boolean remove(Object o) {
        /* 通过双向链表的前后关系,遍历双向链表。
         * 判断是否存在元素和要删除的元素相同。
         * 如果遍历到了,那么就删除元素,并且返回true
         * 如果没遍历到,那么就返回false
         */
        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;
    }
   /**
     * 删除非空节点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 {
        /* 如果前驱不为空,那么就直接采用双向链表删除节点的套路,
         * 此处是解决前驱节点和删除节点的链接关系
         * 直接将前驱节点的后继关系指向后继结点,就解决了前驱节点的后继关系
         * 将删除节点x的前驱设置为空,就解决了删除节点的前驱关系
         */
            prev.next = next;
            x.prev = null;
        }
        //如果后继结点为空,说明当前删除的节点为尾节点,那么就让其前驱节点作为尾节点
        if (next == null) {
            last = prev;
        } else {
        /* 如果后继不为空,那么就直接采用双向链表删除节点的套路,
         * 此处是解决删除节点和后继结点的链接关系
         * 直接将后继节点的前驱关系指向前驱节点,就解决了后继结点的前驱关系
         * 将删除节点x的后继设置为空,就解决了删除节点的后继关系
         */
            next.prev = prev;
            x.next = null;
        }
        //如果上面双向链表删除关系不清楚可以自己画图看看就明白了
        //将删除节点的值设置为空,便于gc
        x.item = null;
        size--;
        modCount++;
        return element;
    }

<br />


<br />
阅读总结:
(1) LinkedList是采用一种双向链表实现的
(2) add方法插入元素使用的是双向链表的尾插法
(3) remove删除指定元素的方法使用的是双向链表的删除节点的套路(就是解除前后链接关系,将对应的元素值设为空。)
(4) LinkedList是采用前后关系来进行遍历的,因为本身也是采用双向链表实现,(正符合链表的结构)

<br />


<br /><br />
.............................该源码为jdk1.7版本的
<br /><br />

相关文章

网友评论

    本文标题:java.util.LinkedList源码解析

    本文链接:https://www.haomeiwen.com/subject/aaiaqxtx.html