LinkedList源码解析

LinkedList特点总结

  • LinkedList实现List接口,使用双向链表实现,元素可以是null
  • 可以被当作堆栈,队列,双端队列使用
  • 因其使用链表实现,查询需要遍历O(n)时间复杂度,插入时不再需要复制移动元素O(1)时间复杂度
  • 类中的iterator()方法和listIterator()方法返回的iterators迭代器是fail-fast的:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件
  • 和ArrayList一样,不是线程安全的,可以使用Collections.synchronizedList()变成支持并发的容器

LinkedList是由一个双向链表来维护的,对于增删改查元素理解最清晰地理解就是画一张图

源码分析

考虑到之前是直接拷贝jdk源码在源码上通过注释的方式进行解读,这样看起来会比较杂乱,所以这次采用分块解读的方式

  1. 节点类

因为采用链表的方式实现,所以需要先定义一个结点类,源码中使用静态内部类的方式定义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;
        }
    }

可以看到定义的是一个双向链表的Node类,next指向其下一个Node,prev指向其上一个Node

  1. LinkedList的成员变量

    transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

size是当前List中的元素个数,first和last都是Node类型的变量,分别指向链表的第一个节点和最后一个节点,这三个变量都是transient,说明不希望进行序列化

  1. 构造方法
    public LinkedList() {
    }

    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

第一个构造方法创建了一个空的LinkedList,第二个构造方法是通过一个Collection接口的实现类对象进行初始化,首先调用第一个构造方法创建一个空的LinkedList然后将参数指向的Collection中的元素添加到LinkedList中,元素的顺序是Collection的iterator返回的顺序

  1. 增加元素

LinkedList提供了头插入addFirst(E e)、尾插入addLast(E e)、add(E e)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、add(int index, E element)这些添加元素的方法

源码中提供了几种增加元素的方法

  public boolean add(E e) {
        linkLast(e);
        return true;
    }

首先看add方法,add方法中调用了linkLast方法,可以看出来是在list的末尾添上一个Node,这个方法与源码中的addLast方法效果是相同的,只是add方法是有返回的,凡是返回比较鸡肋

   public void addLast(E e) {
        linkLast(e);
    }

顺藤摸瓜,我们来看一个linkLast方法

   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++;
    }
  • 首先通过last指针找到list的最后一个node,然后创建一个新的node,这个newNode的next指向null,prev指向链表的最后一个节点
  • 将链表的last指向创建的newNode
  • 判断之前找到的last node 如果该node是null说明原来list中没有元素,将创建的newNode赋值给first表示新node是最后一个元素也是第一个元素,如果l不是null,表示原来的list中有元素,此时将l的next指向newNode
  • 最后需要将size++ 表示list元素多了一个

除了在list的尾巴上添加元素,jdk源码中还提供了在链表头上添加元素的方法

   public void addFirst(E e) {
       linkFirst(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++;
   }

addFirst会直接调用linkFirst完成插入操作,和上面linkLast方法其实是相仿的

  • 首先找到first指向的元素,有可能是一个null
  • 创建一个新的node,其prev指向null,因为newNode是链表的第一个元素
  • first指向newNode
  • 如果f指向null,表示链表原先是没有元素的,那么newNode就是当前唯一的元素,last也要指向newNode,如果f不是null,表示原先链表中就有元素,需要将f指向的(此时是链表的第二个元素)的prev指向newNode

LinkedList同时也提供了指定index插入元素

    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

  • 首先进行index的检查,保证index在[0,size]之间,如果是非法的index就抛出一个异常
  • 如果index==size 就是在链表的最后进行添加
  • 否则调用linkBefore方法,完成元素添加
   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;
       }
   }

node(int index)这个方法返回在链表index位置上的node,返回的node是一个不为null的node,// assert isElementIndex(index);被注释掉是因为前面已经进行index == size 判断就不再这边进行判断了,此外需要注意的是这边使用了一个小技巧

  • 如果index属于链表的前面一半 index < (size >> 1) 就从first指向的元素进行遍历
  • 如果index属于链表的后面一半元素 就从last指向的元素进行遍历
  • 返回node
    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++;
    }

linkBefore方法是最终完成插入操作的方法,同样succ!=null也被注释掉了,因为上面node方法返回的是一个不为null的node,linkBefore其实是一个双向链表的插入操作

  • 首先找到succ这个node prev指针指向的node,即succ左手边的一个node
  • 创建一个新的node,这个newNode prev指向pred,next指向succ
  • 因为jdk中在add(index,e)中仅对index==size的情况做了特殊处理,并没有对index==0的时候做出特殊处理,即在第一个位置进行插入,此时pred会是null,当pred == null时表示index == 0在第一个位置进行插入了,pred右手边指针需要指向newNode,其他情况下pred.next指向newNode即可
  1. 访问元素

LinkedList提供了getFirst()、getLast()、contains(Object o)、get(int index)、indexOf(Object o)、lastIndexOf(Object o)这些查找元素的方法

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

首先进行index检查,然后通过node(index)获取index下的元素,最后通过node.item获取到值,需要注意index检查的时候与add index检查的方法存在一点点细微的区别:index >= 0 && index < size;这里index必须在[0,size-1]

jdk还提供了一些取特殊位置元素的方法

    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;
    }
  1. 删除元素

LinkedList提供了头删除removeFirst()、尾删除removeLast()、remove(int index)、remove(Object o)、clear()这些删除元素的方法

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

首先index检查与上面get中的方式相同,然后调用找到index指向的元素,调用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;
    }

因为node(index)方法返回一个非null的node,所以这边把 x != null注释掉了,unlink是一个双向链表删除的操作

  • 首先获取x的值,值作为最后的返回
  • 分别获取x next和prev指向的元素
  • 如果prev==null 表示要删除的是第一个元素,同样,如果next == null表示要删除的是最后一个元素
  • 如果prev==null 将first指向链表的第二个元素即next指向的元素,否则prev.next = next;x.prev = null;
  • 如果next==null 表示要删除的是最后一个元素,last = prev;表示将x前面的一个元素作为链表的最后一个元素,否则 next.prev = prev;x.next = null;
  • 最后x.item = null size-- modCount++
    最好的方式还是画一张图帮助理解

同样的,jdk也提供了一些在特殊位置删除元素的方法

    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
        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;
    }

    private E unlinkLast(Node<E> 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;
    }

不详细展开了,需要注意的是比如删除第一个节点的时候需要注意链表只有一个元素的情况

  1. 修改元素的值
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

需要注意最后返回原先的值

  1. 其他常见操作
    public int size() {
        return size;
    }

返回当前链表中元素的个数

   public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

    public int indexOf(Object o) {
        int index = 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;
        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;
    }

返回元素o在链表中的index,准确说应该是第一个o的index,与ArrayList中处理的方式是一样的,遍历的时候对null和non-null做了判断

  1. Queue操作

Queue操作提供了peek()、element()、poll()、remove()、offer(E e)这些方法

    //获取但不移除此队列的头;如果此队列为空,则返回 null
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    //获取但不移除此队列的头;如果此队列为空,则抛出NoSuchElementException异常
    public E element() {
        return getFirst();
    }

    //获取并移除此队列的头,如果此队列为空,则返回 null
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    //获取并移除此队列的头,如果此队列为空,则抛出NoSuchElementException异常
    public E remove() {
        return removeFirst();
    }

    //将指定的元素值(E e)插入此列表末尾
    public boolean offer(E e) {
        return add(e);
    }

Deque操作提供了offerFirst(E e)、offerLast(E e)、peekFirst()、peekLast()、pollFirst()、pollLast()、push(E e)、pop()、removeFirstOccurrence(Object o)、removeLastOccurrence(Object o)这些方法

 //获取但不移除此队列的头;如果此队列为空,则返回 null
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    //获取但不移除此队列的头;如果此队列为空,则抛出NoSuchElementException异常
    public E element() {
        return getFirst();
    }

    //获取并移除此队列的头,如果此队列为空,则返回 null
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    //获取并移除此队列的头,如果此队列为空,则抛出NoSuchElementException异常
    public E remove() {
        return removeFirst();
    }

    //将指定的元素值(E e)插入此列表末尾
    public boolean offer(E e) {
        return add(e);
    }

    // Deque operations

    //将指定的元素插入此双端队列的开头
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

    //将指定的元素插入此双端队列的末尾
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

    //获取,但不移除此双端队列的第一个元素;如果此双端队列为空,则返回 null
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

    //获取,但不移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

    //获取并移除此双端队列的第一个元素;如果此双端队列为空,则返回 null
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    //获取并移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null
    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) {
        //由于LinkedList中允许存放null,因此下面通过两种情况来分别处理
        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;
    }
  1. 遍历方式
  • 使用迭代器iterator遍历
Iterator iter = list.iterator();
   while (iter.hasNext())
   {
       System.out.println(iter.next());
   }    
  • 使用listIterator遍历
ListIterator<String> lIter = list.listIterator();
  //顺向遍历
  while(lIter.hasNext()){
      System.out.println(lIter.next());
  }
  //逆向遍历
  while(lIter.hasPrevious()){
      System.out.println(lIter.previous());
  }
  • 使用foreach遍历
 for(String str:list)
    {
        System.out.println(str);
    }    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 82,587评论 1 181
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 29,411评论 1 149
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 34,341评论 0 105
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 18,870评论 0 90
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 24,188评论 0 150
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 20,024评论 1 90
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 12,687评论 2 168
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 12,083评论 0 83
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 10,611评论 5 116
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 13,868评论 0 132
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 12,550评论 1 130
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 13,421评论 0 135
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 8,177评论 0 18
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 10,999评论 2 122
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 14,185评论 3 131
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 9,787评论 0 3
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 10,134评论 0 81
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 14,765评论 2 140
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 15,240评论 2 136

推荐阅读更多精彩内容