LinkedList源码浅析

回顾集合体系中LinkedList的源码实现,查看的源码为Android系统中的java源码实现,和JDK中的实现可能有一定区别。

成员变量

    //存储的元素数量
    transient int size = 0;
    //队首元素节点
    transient Node<E> first;
    //队尾元素节点
    transient Node<E> last;

构造方法

//无参数构造
    public LinkedList() {
    }
    //传入集合的构造器
    LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);//最终调用了addAll方法,后面分析
    }

元素节点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;
        }
    }

add方法

//添加指定元素到队尾
    public boolean add(E e) {
        linkLast(e);//调用linkLast方法添加
        return true;
    }

    //将指定元素链接到队尾
    void linkLast(E e) {
        final Node<E> l = last;//当前的队尾元素
        //将当前的队尾last对象和要添加的元素e封装成newNode,使其
        //newNode的prev成员变量持有last对象,这样完成向前的链接
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;//然后将newNode赋值给last,完成队尾更新
        if (l == null)//如果last对象是null,说明还没有任何元素添加进来
            first = newNode;//直接将newNode赋值给first,也就是队首节点
        else
            l.next = newNode;//不是null,就把last的next的引用指向newNode,完成向后的链接
        size++;//size自增
        modCount++;//修改标识自增
    }

在指定位置插入元素:

    //在指定的索引插入元素 
    public void add(int index, E element) {
        checkPositionIndex(index);//检查索引是否合法

        if (index == size)//如果index和size相同,那么直接插入队尾
            linkLast(element);
        else//如果index和size不相同,那么调用linkBefore插入
            linkBefore(element, node(index));
    }

     //取出指定索引存储的Node信息
    Node<E> node(int index) {
        // assert isElementIndex(index);
        //如果index小于size/2,那就从0遍历到index-1,返回x
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            //如果index大于size/2,那就从size-1向前遍历到index+1,返回x
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

 //在指定的节点前插入给定的元素
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;//先取出节点的前一个节点对象
        //然后将前后节点和当前元素封装成newNode,建立双向链接
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;//然后把newNode赋值给被插入对象的prev引用,建立向前引用
        if (pred == null)//如果pred对象为null,说明到了队首,赋值给first
            first = newNode;
        else//如果不为null,那么将pred的next节点赋值为newNode,完成向后引用
            pred.next = newNode;
        size++;//size自增
        modCount++;//修改标识自增
    }

在队尾添加集合的方法:

    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
 //在指定的索引插入一个集合
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);//检查索引合法性
        //转换为数组判断长度是否合法,为0直接返回false
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
        //定义向前和向后的引用    
        Node<E> pred, succ;
        if (index == size) {//如果index==size说明是要插入到队尾
            succ = null;//所以向后的引用就没有了
            pred = last;//向前的引用就是当前的last
        } else {//不等的话,就先取出index对应的节点
            succ = node(index);//向后引用直接使用该节点
            pred = succ.prev;//向前的引用赋值该节点的prev引用
        }

        for (Object o : a) {//循环插入的数组
            @SuppressWarnings("unchecked") E e = (E) o;
            //封装newNode对象,完成向前引用
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)//如果pred是null,说明到了队首
                first = newNode;//将newNode直接赋值给first
            else//如果pred不为Null,那么将newNode赋值给pred.next,完成向后链接
                pred.next = newNode;
            pred = newNode;//然后将newNode赋值给pred,继续下一轮链接,直到完成
        }
        //succ为null,说明是插入队尾的,将最后的pred赋值给last
        if (succ == null) {
            last = pred;
        } else {//不为Null
            pred.next = succ;//pred的向后引用指向succ
            succ.prev = pred;//succ的向前引用指向pred
        }

        size += numNew;//size增长
        modCount++;//修改标识自增
        return true;
    }

push方法

push方法是将指定元素添加到队首

    public void push(E e) {
        addFirst(e);
    }
        public void addFirst(E e) {
        linkFirst(e);
     }
     //最终调用该方法将元素添加到队首   
        private void linkFirst(E e) {
        final Node<E> f = first;//取队首节点封装到newNode 
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;//将newNode赋值到first队首
        if (f == null)//如果f也就是原来的队首是Null,说明没有之前没有任何元素
            last = newNode;//这样将newNode也赋值给last
        else//否则就把原来队首的向前引用指向newNode
            f.prev = newNode;
        size++;//size自增
        modCount++;//修改标识自增
    }

get方法

获取知道索引的元素的方法:

    public E get(int index) {
        checkElementIndex(index);//检测索引是否合法
        return node(index).item;//调用了node方法,取出节点包含的元素
    }

获取队首元素方法:

    public E getFirst() {
        final Node<E> f = first;
        if (f == null)//为null就抛出异常
            throw new NoSuchElementException();
        return f.item;//返回的就是first节点包含的元素
    }

获取队尾元素方法:

    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;//返回的就是last节点包含的元素
    }

set方法

    public E set(int index, E element) {
        checkElementIndex(index);//检测索引合法性
        Node<E> x = node(index);//取出index指向的node
        E oldVal = x.item;//取出node包含的元素
        x.item = element;//直接赋值新元素
        return oldVal;//返回旧元素
    }

remove方法

移除指定索引的元素的方法:

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));//先调用node方法取出对应的节点,然后调用unlink方法
    }

E unlink(Node<E> x) {
        // assert x != null;
        //取出待移除的node包含的元素,和前后的引用节点
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {//如果向前引用prev为Null,说明移除的是队首元素
            first = next;//将向后的引用next赋值给first
        } else {//不为Null,就将prev的向后引用赋值next,完成向后的链接
            prev.next = next;
            x.prev = null;//原始的prev置Null
        }

        if (next == null) {//如果next为null,说明移除的是队尾
            last = prev;//就将prev赋值给last
        } else {//不是队尾就把next的向前引用赋值为prev,完成向前的链接
            next.prev = prev;
            x.next = null;//原始的next赋值为Null
        }

        x.item = null;//然后把原始的元素置null
        size--;//size自减
        modCount++;//修改标识自增
        return element;//返回旧元素
    }

pop方法

该方法就是移除队首元素,并返回移除的元素:

    public E pop() {
        return removeFirst();
    }
   public E removeFirst() {
      final Node<E> f = first;
     if (f == null)
        throw new NoSuchElementException();
      return unlinkFirst(f);
    }
    //最终调用该方法来移除队首元素
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;//取出包含的旧元素
        final Node<E> next = f.next;//取出队首的向后引用的next节点
        f.item = null;//将原始元素置null
        f.next = null; // help GC next也置null,GC负责回收
        first = next;//然后队首first置为next
        if (next == null)//如果next是null说明之前只包含一个元素
            last = null;//那么移除后last也置Null
        else//否则,将新队首向前的引用置null
            next.prev = null;
        size--;//size自减
        modCount++;//修改标识自增
        return element;//返回旧元素
    }

size方法

    public int size() {
        return size;//直接返回size
    }

isEmpty方法

该方法在抽象类AbstractCollection中定义:

    public boolean isEmpty() {
        return size() == 0;//最终也是判断的size
    }

indexOf方法

找出指定元素出现的第一个索引方法:

    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {//如果o为Null,从前往后循环,找出第一个为Null的元素,返回其索引
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {//如果o不为Null,从前往后循环,找出第一个和O相同的元素,返回其索引
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;//找不到返回-1
    }

lastIndexOf方法

找出指定元素出现的最后出现的索引方法:

    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {//如果o为Null,从后往前循环,找出第一个为Null的元素,返回其索引
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {//如果o不为Null,从后往前循环,找出第一个和O相同的元素,返回其索引
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;//找不到返回-1
    }

contain方法

    public boolean contains(Object o) {
        return indexOf(o) != -1;//其实也是调用的indexOf方法
    }

clear方法

    public void clear() {
        // Clearing all of the links between nodes is "unnecessary", but:
        // - helps a generational GC if the discarded nodes inhabit
        //   more than one generation
        // - is sure to free memory even if there is a reachable Iterator
        //就是从前往后遍历,将所有的引用置为Null
        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;//最后将队首和队尾置null
        size = 0;//size为0
        modCount++;//修改标识自增
    }

总结

  • 由于LinkedList为双链表设计,ArrayList本质为数组实现,因此比较ArrayList主要区别:

转自 温布利往事:Java集合之LinkedList源码分析

ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间,就存储密度来说,ArrayList是优于LinkedList的。  
  总之,当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能,当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。

  • 由于LinkedList实现了Deque双端队列接口,和List接口,因此存在功能相同的方法;
  • 实现了CloneableSerializable接口;

参考文章
Java集合之LinkedList源码分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值