LinkedList源码分析

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • 实现了List接口,代表着可以实现精准控制,即所谓的按照index进行一系列操作。
  • Deque接口,代表底层是双端队列
  • Cloneable,Serializable 拷贝和序列化
  • 不支持随机访问
基本组成
transient int size = 0;

transient Node<E> first;

transient Node<E> last;

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;
    }
}    

头节点要么是null,要么头结点的前向节点是null;尾节点要么是null,要么尾节点的后向节点是null

可以看到双向链表的基本组成单元就是Node。节点的一些定义不再赘述,无非就是值和只想前后节点的指针。同时LinkedList不存在容量的问题,所以构造函数相比ArrayList少了指定容量的方式,其他基本一致。

添加元素
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++;
}

因为是在头部插入元素,所以构造的新节点的next指针指向当前的头节点,如果原来头节点是空(链表中没有元素),将链表的尾节点也设为该新节点;否则的话,将原头节点的前向节点指向新节点
添加元素的方法有好几种,用的时候注意添加的顺序,可能是添加到头部或者尾部,建议使用名字较为明显的这种方式,添加到头部的还有push。

添加到尾部的方法有add,offer,addLast,当然对应的实现方法中都调用了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节点,若last节点是null,说明是空链表,此时新节点也是头节点。否则的话,将原last节点指向新节点。

删除元素

与添加节点类似,删除元素也分为删除头部元素和删除尾部元素,remove/removeFirst/poll/pop 都是删除头部元素,只有removeLast是删除尾部元素
就挑其中一种简要分析下

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;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

根据节点构造出元素,最后要返回这个元素。

因为要删除的是头节点,所以将该节点值置为null,next指针也置为null,方便gc回收,如果下个节点是空,说明变成了空链表,将尾节点置为null,否则的话,将下个节点的prev置为null。

序列化
private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out size
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (Node<E> x = first; x != null; x = x.next)
            s.writeObject(x.item);
    }

在前边的ArrayList中数组是transient,我们提到了这样防止使用java默认的序列化,因为ArrayList是动态扩容的,数组中并不一定被填满,所以采用了自己写的write/readObject 继续序列化,这样效率更高,这里的LinkedList类似,first和last节点也是transient。

总结

LinkedList底层使用双向链表实现,元素间内存地址不连续,只能使用指针定位,所以便利的时候不要用for,最好用foreach或者迭代器,不然每次都是从头节点开始去找。另外相比ArrayList,LinkedList添加删除的空间代价较小,毕竟只需要指针动一下,不用大费周章。

关注公众号: 程序员二狗

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值