java集合-LinkedList源码详解

导言:
在实际应用开发的过程中,对于数据的操作我们常常考虑这样的问题:需要快速搜索成千上万个有序序列吗?需要快速插入删除有序序列吗?需要建立键值之间的关联吗?当非常关注性能时,选择不同的数据结构会带来很大的差异。



前言

LinkedList在java集合中的位置
在这里插入图片描述
LinkedList是一种屋里存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点组成,结点可以在运行时动态生成,LinkedList中有两个指针,前指针和后指针,是一个双向链表;
链表的增删由于不需要移动底层数组数据,其底层是链表实现的,只需要修改链表节点指针,所以效率较高。

一、LinkedList的继承和接口实现

源码(jdk 1.8)

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  • 可以看到LinkedList继承了AbstractSequentialList抽象类(已完成接口的大部分函数,部分函数根据自己的链表结构进行了重写,如toArray()),此外LinkedList实现了Deque(双向队列的接口,扩展自queue接口),对于List、Cloneable、Serializable接口在ArrayList源码介绍中有讲到,不再赘述。
  • 也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。。当你需要使用栈或者队列时,可以考虑使用LinkedList,一方面是因为Java官方已经声明不建议使用Stack类,更遗憾的是,Java里根本没有一个叫做Queue接口的类。关于栈或队列,现在的首选是ArrayDeque,它有着比LinkedList(当作栈或队列使用时)有着更好的性能。

二、LinkedList底层实现

1.底层结构

结点结构源码:

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

链表源码

  transient int size = 0;
  //记录链表中元素的个数 ,transient可避免序列化此字段
    transient Node<E> first;
    //链表的头结点
    transient Node<E> last;
    //链表的尾结点

在这里插入图片描述

  • 一个结点包含了元素值和前后结点的引用,对于首节点其前结点为NULL,对于尾结点其后结点为NULL
  • 对于一个链表我们只需知道其首尾结点即可

2.增删改查

LinkedList 增加元素方法

  • addFirst 在首结点之前
  • addLast 在尾结点之后
  • add 在尾结点之后,可提供index参数指定位置
  • addAll 增加参数集合中全部元素到尾结点之后
  • offer 调用add()
  • offerFirst 调用addFirst()
  • offerLast 调用addLast ()
  • push 调用addFirst()
  • 总结下来,虽然函数多,其内部实现是调用的,如offerFirst 、push 内部均是调用了 addFirst()函数
    addFirst函数源码:
 public void addFirst(E e) {
        linkFirst(e);  //调用了linkFirst
    }
     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++;  //操作数增加
    }

在这里插入图片描述

addAll源码

public boolean addAll(int index, Collection<? extends E> c) {//在index位置处,加入集合c全部元素
        checkPositionIndex(index);//索引是否出界

        Object[] a = c.toArray(); //集合转换为数组形式
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;  //记录被增加元素的前后结点
        if (index == size) {//相当于从链表尾结点开始加入元素
            succ = null;
            pred = last;
        } else {//否则
            succ = node(index);//这里的node函数是获取索引为index的那个结点
            pred = succ.prev;
        }

        for (Object o : a) {  //for循环
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }

        if (succ == null) { //如果源链表index位置处的后一个结点为null
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

删除和增加对应即可,后面讲解一下查询

  • peek
  • getFirst :头结点值
  • getLast :尾结点值
  • get :参数index 获取index处的结点值
  • peek :头结点值
  • element :调用getFirst
  • peekFirst:头结点值
  • peekLast :尾结点值
  • pollFirst :头结点值,并删除头结点
  • pollLast :尾结点值,并删除尾结点
  • pop :头结点值,并删除头结点
  • lastIndexOf :查找最后一次出现的index, 如果找不到返回-1;
  • indexOf :查找第一次出现的index, 如果找不到返回-1;
    通过下标获取某个节点的时候,会根据index处于前半段还是后半段进行一次折半,以提升查询效率;而改和查,都需要先定位到目标节点,所以效率较低;
    如下:
Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) { //index小于size/2,从前遍历
            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;
        }
    }

三、LinkedList其他

clear()

为了让GC更快可以回收放置的元素,需要将node之间的引用关系赋空。

 public void clear() {
        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;
        size = 0;
        modCount++;
    }


Fail-Fast机制 modCount

同ArrayList也采用了快速失败的机制,通过记录modCount参数(操作数)来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

四、另言

`

  • 因为LinkedList是一个链表结构,所以可理论上存取无限的数据,但是所造成的数据访问效率低下。

  • 可动态添加删除大小可变 ,内存可能是不连续内存,链式存储。

  • 为追求效率LinkedList没有实现同步(synchronized),如果需要多个线程并发访问,可以先采用Collections.synchronizedList()方法对其进行包装。

    欢迎朋友讨论指正哈~ ~

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值