LinkedList源码学习笔记

目录

JDK集合源码学习-LinkedList集合

1、使用ArrayList集合优缺点

2、了解完之前ArrayList的一些特点之后,简单学习LinkList集合

这里先介绍一下linked数据结构

2.1  不啰嗦,直接看源码

总结:

LinkedList的特点

使用场景:

linkedList的使用场景


JDK集合源码学习-LinkedList集合

在java开发中List集合被常常使用,对于上一篇文章简单记录了ArrayList源码的学习笔记。

从ArrayList源码分析中我们不难总结出以下几点:

1、使用ArrayList集合优缺点

1.1  ArrayList集合底层使用的数组实现,我们都知道数组容量是固定大小,所以我们在使用的时候需要尽量预估我们数据的多少。

1.2  由于底层是数组实现,操作时寻址很快,但是ArrayList操作相关方法如 add(),add(int index ,Obj o),remove()等都可能会造成数组之间的拷贝和扩容问题。如果增删数据较少影像不大,如果数据量大,造成批量copy数据无疑会增加时耗。

1.3  ArrayList虽然增删操作慢,但get数据和寻址非常快。使用for遍历ArrayList集合效率非常快。

2、了解完之前ArrayList的一些特点之后,简单学习LinkList集合

这里先介绍一下linked数据结构

LinkedList是基于双向链表实现,每个节点中有pre指针,element值,next指针;

pre指针指向上一个Node节点,item存放element的值,next指针指向下一个元素,依次类推;

具数据体结构简易图如下: 

2.1  不啰嗦,直接看源码

2.1.1 尾部增加一个元素-add(E e)和addLast(E e)一样

public boolean add(E e) {
    linkLast(e); // linkedList 直接add方法 就是在尾部创建一个节点并赋值增加指针等
    return true;
}
public void addLast(E e) {
    linkLast(e);  //最终调用linkLast方法
}

if (l == null)
    first = newNode; //当linkedList是空集合的时候,新创建的newNode就作为第一个节点

 2.1.2  在中间增加一个元素 addFirst(E e) 和 add(int index,E e)类似

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); //addFirst调用,pred指向空,next指向原来集合中第一个元素
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

 

 

 2.1.3  set(int index,E e)方法

public E set(int index, E element) {
    checkElementIndex(index); //检查set的index是否越界
    Node<E> x = node(index);   //遍历链表直到下标为index的节点
    E oldVal = x.item;        //获取原来节点的item
    x.item = element;        //给原来节点的赋值item=element;
    return oldVal;
}

2.1.4  get(int index)方法

public E get(int index) {
    checkElementIndex(index);
    return node(index).item; // node(index)方法
}
Node<E> node(int index) {
    // assert isElementIndex(index);
//判断index位置是在前半部分还是后半部分,size>>1 相当于size/2
    if (index < (size >> 1)) {
        //要获取的元素在集合的前半部分
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;  //从first挨个向后遍历 直到index-1的下一个元素 即是要获取的Node
        return x;
    } else {
        //要获取的元素在集合的后半部分
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;      //从last往前遍历 直到index+1的前一个元素 即是要获取的Node
        return x;
    }
}

 2.1.5  LinkedList的remove操作

remove的几种方法:remove()、remove(Obj o)、remove(int index) 、removeLast()等

public E remove() {
    return removeFirst(); //移除头结点
}
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);        //具体的删除头结点代码 与 删除last结点类似都是极端
}
public boolean remove(Object o) {        //该方法与remove(int index)类似,remove(int index) 先调用node(index)从头遍历直到找到index位置的节点;
//该方法中遍历与remove(int index)区别:该方法只能从头挨个遍历,如果要删除的位置在最后需要遍历整个链表,而remove(int indet)方法中调用node(index)可以通过index判断在链表的位置,最多遍历半个链表
    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;
}
E unlink(Node<E> x) {                //移除最终调用的方法 简单来说就是让移除的节点 pre指针,next指针都指null,同时之前pred节点的next指针指向next节点,next节点的pre指针指向pred节点。item也等于null,被孤立起来的节点最终会被删除、GC清理
    // assert x != null;
    final E element = x.item;             // 取出将被移除节点存放的数据
    final Node<E> next = x.next;        //原先将被移除节点next指针指向的节点
    final Node<E> prev = x.prev;        //原先将被移除节点prev指针指向的节点

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;        //pred节点的next指针指向next节点
        x.prev = null;        //将被移除的节点prev指针指null
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;        //next节点的prev指针指向原来的pred节点
        x.next = null;        // 将被移除的节点的next指针 指向null
    }

    x.item = null;        // 将被移除的节点的item值设置null
    size--;
    modCount++;
    return element;        //返回被移除的节点数据(第一行:element = x.item)
}

移除前后两张图 

 

 

 

2.1.6  LinkedList也可以作为简单的队列使用

方法有offer,offerFirst,offerLast等,源码调用的方法是add相关方法

public boolean offer(E e) {
    return add(e);
}
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}
public boolean offerLast(E e) {
    addLast(e);
    return true;
}

2.1.6  peek方法,poll方法 

public E peek() {
    final Node<E> f = first;                 //直接获取头结点的item
    return (f == null) ? null : f.item;
}
public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;   //直接获取头结点的item
 }
public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;      //直接获取最后结点的item
}
public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);        // 获取元素后删除该节点
}
public E pollFirst() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);     // 获取头结点元素后删除该节点
}
public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);      //获取最后节点元素后删除该节点
}

队列中使用的方法基本都于之前方法类似,peek和poll方法会判断空,为空返回空值。getFirst,getLast,removeFirst和removeLast方法不判断空,如果为空抛出异常NoSuchElementException.

总结:

LinkedList的特点

linkedList底层使用的是双向链表

通过前面的分析之后,我们可以了解到大量add和remove操作的时候不会导致扩容问题和大量拷贝数组的问题,这是LinkedList的强项。

get操作,首先会判断要获取的index位置在链表的前半部分还是下半部分,如果是在上半部分从First节点开始遍历直到找出获取的节点数据;如果index>size>>1 那么要获取的节点在链表后半部分,则从Last节点开始往前遍历。所以遍历数据LinkedList弱项,遍历数据是ArrayList的强项,可以随机获取。

addFirst,addLast等在两个段增删操作时性能要比在中间操作效率高,因为在中间操作比如add(index,e)或者remove(index),remove(e)等方法会先去走一个遍历的操作。

相对ArrayList来说,LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中的每个节点中存储的是实际的数据和前后节点的位置。

使用场景:

linkedList的使用场景

综合上述特点,如果应用程序对数据有较多的随机访问,ArrayList对象要优于LinkedList对象;

 如果应用程序有更多的插入或者删除操作,较少的随机访问,LinkedList对象要优于ArrayList对象;不过ArrayList的插入,删除操作也不一定比LinkedList慢,如果在List靠近末尾的地方插入,那么ArrayList只需要移动较少的数据,而LinkedList中remove(Obj o)移除时则需要一直查找到列表尾部,反而耗费较多时间,这时ArrayList就比LinkedList要快。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
LinkedList是一种基于双向链表的List数据结构。它的内部实现源于对链表的操作,所以适用于频繁增加和删除元素的情况。LinkedList的创建可以通过构造器来完成,而往LinkedList中添加对象可以使用add方法。LinkedList不是线程安全的,并且查询元素的效率相对较低,因为需要一个一个遍历链表来查找元素。LinkedList实现了Queue接口,所以它既可以作为队列使用,也可以作为栈使用。此外,LinkedList还实现了Deque接口,即双端队列。与ArrayList相比,LinkedList的内部结构有所不同,LinkedList继承自AbstractSequentialList,然后再继承自AbstractList。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [第三章 LinkedList源码解析1](https://download.csdn.net/download/weixin_35760849/86324281)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [LinkedList源码解析](https://blog.csdn.net/qq_38826019/article/details/115677359)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nikola TesIa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值