Java集合体系LinkedList特点以及常用方法内部剖析

LinkedList

  1. 特点
    1. 允许null值
    2. 内部以双向链表的形式来保存集合中的元素查询慢,增删快(相比于ArrayList少了数组拷贝)
    3. 线程不安全
    4. 所有指定位置的操作都是从头开始遍历进行的
    5. 素是有序的,输出顺序与输入顺序一致

2.理论与实操同样重要,知其然也要知其所以然

LinkedList查询慢,增删快与ArrayList相反,但同时都是线程不安全的

首先分析其构造方法
//空参构造(使用最多)
public LinkedList() {
    }
//有参构造
public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);//添加所有集合元素
    }
分析方法实现原理源码

1. 添加方法其一

  • 原理:
    首先看单向链表添加如何实现:
    点击这里可以自行操作观察
    在这里插入图片描述
    每次增加一个元素,最后一个元素节点会指向新增加元素(2指向3),我习惯称为前一个元素的后继集节点指向这个元素
    同理,在java中,因为是双向链表所以,还有一个节点,我习惯称为元素的前驱节点指向前一个元素(3指向2)
    下面探究java中如何实现这一操作
/
 public boolean add(E e) {
        linkLast(e);//实则调用这个方法,如下
        return true;
    }
/**
 * 先分析: 1、双向链表包括首节点first,尾节点last,元素节点
 *        2、每一个元素节点包括三部分:元素、前驱节点、后继节点
 *        3、添加元素又分为首次添加、非首次添加
 *        4、首次添加:first为空节点,last为空节点,此时创建新元素节点
 *      前继节点则为空,后继节点也为空,添加元素后,此元素节点既是first也为last
 *        5、非首次添加:创建新元素节点,添加到链表最后,此时应让新元素节点的前驱节点指向当前尾节点(在创建新节点时调用的构造函数中完成)
 *      然后新元素变为新的尾节点,因为是双向链表,需要之前尾节点的后继节点同样指向新的尾节点节点完成双向
 */
 void linkLast(E e) {
        final Node<E> l = last;//此时l代表当前尾节点
        /**创建一个节点:l代表尾节点(元素前驱节点)、e当前节点、null代表尾节点(元素后继节点)*/
        final Node<E> newNode = new Node<>(l, e, null);//Node方法如下文
        //此时让尾节点指向新添加的元素,作为尾节点的标记(上述新的尾节点)
        last = newNode;
        if (l == null)//在第一次添加元素时,last为空所以l为空,则这个元素就是第一个节点(首节点)
            first = newNode;//引出首节点,作为首节点的标记(同时也是尾节点)
        else//若不是第一次添加元素,则尾节点last不为空,l则也不为空,
            l.next = newNode;//(为了满足双向链表)当前尾节点的后继节点指向新添加的元素节点,至此新添加元素节点变为尾节点(完成双向链接)
        size++;//链表元素个数加一
        modCount++;//链表操作次数记录加一
    }
 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;//在创建新节点时,前继节点在这里完成了指向当前尾节点
        }
    }

2. 添加方法其二

原理:时刻考虑双向链表特性,指定位置添加元素时,改变新增元素前后继节点,以及后元素节点的前指向,和前元素节点的后指向,下图上方为新增节点,在1处新增示意图(大概一个示意图,有点丑),与上述唯一区别就是此方法时在中间添加元素
在这里插入图片描述
源码:

//index指定添加元素的位置,element元素值
 public void add(int index, E element) {
 		//判断索引是否越界或者非法(小于零或者大于链表长度)
        checkPositionIndex(index);
		//若index==size证明需要在链表末尾添加元素(和第一种添加方法实现一致)
        if (index == size)
            linkLast(element);//同上方法一
        else//反之,在指定索引添加元素 首先node(index)找出该索引对应的元素节点
            linkBefore(element, node(index));
    }

void linkBefore(E e, Node<E> succ) {
        // 获取指定位置元素的前驱节点
        final Node<E> pred = succ.prev;
        //创建新节点,前驱节点指向原指定位置的前继节点,后继结点指向succ节点
        //例:1<——2(newNode)——>3
        final Node<E> newNode = new Node<>(pred, e, succ);
        //上述例子 因为是双向链表所以要同时让2<——3
        succ.prev = newNode;
        if (pred == null)
        //pred 为空证明新添加的元素是第一个节点,那么更新首节点
            first = newNode;
        else
        //反之,也要设置1——>2,保证双向链表
            pred.next = newNode;
        //元素个数加一
        size++;
        //记录操作次数
        modCount++;
    }

其他的添加方法原理基本一样

3. 删除方法

  • 原理就是完成下图的整个过程(有了想法才有可能实现!)

在这里插入图片描述指定对象删除

public boolean remove(Object o) {
        if (o == null) {//若删除null元素
            for (Node<E> x = first; x != null; x = x.next) {//从第一个元素开始,首节点给x,判断条件是x是否有值,有的话进入循环体,然后将x的后继节点付给x(后继节点就相当于下一个元素)
                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;
    }

指定索引删除

public E remove(int index) {
        checkElementIndex(index);//判断索引是否为合法(小于零或者大于链表长度)
        return unlink(node(index));//node(index)找到元素节点直接删除如下
    }


 Node<E> node(int index) {
        //根据索引查找元素这里体现了双向链表
        if (index < (size >> 1)) {//index小于元素总个数的一半,从前找更快
        	//从前往后找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;//顺藤摸瓜找到index位置元素
            return x;
        } else {//反之从后找更快
        //从后往前找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

发现上述方法就是通过传入的条件找到了要删除的元素最终通过unlink()方法删除下面分析这个方法

 E unlink(Node<E> x) {
 		//x就是要删除的节点,从节点中取出要删除的元素
        final E element = x.item;
        //取出后继节点
        final Node<E> next = x.next;
        //取出前驱节点
        final Node<E> prev = x.prev;
		//下方两个if语句的执行代表了上图过度过程的实现(解链操作)
        if (prev == null) {//若前驱节点为null证明,这是第一个元素
            first = next;//将要删除的元素节点的下一个元素节点作为第一个元素节点(后继节点指向的元素)
        } else {//反之则为中间元素节点
            prev.next = next;//上图中过度过程的上方蓝线形成1的后继节点指向3(通过2),同时1指向2红线断裂(被重新赋值覆盖了)
            x.prev = null;//断开2的前驱节点(断开了2指向1的那条红线)
        }

        if (next == null) {//若是最后一个元素
            last = prev;//将最后一个元素的前一个元素设置为最后一个元素,记录到尾节点中
        } else {
            next.prev = prev;//上图中过度过程的下方蓝线形成3的前驱节点指向1(通过2)同时3指向2红线断裂(被重新赋值覆盖了)
            x.next = null;//断开2的后继节点(断开了2指向3的那条红线)
        }
		//最后清空元素,到此元素节点被成功删除
        x.item = null;
        size--;//元素个数自减
        modCount++;//操作次数自增
        return element;//返回这个被删除的元素
    }

掌握上述方法后再看其他相关删除方法代码,逻辑都一样

4. 查找元素get()方法

 public E get(int index) {
        checkElementIndex(index);
        return node(index).item;//在删除方法中已经说过node方法,得到节点后取出元素返回
    }

5. 遍历

  • 原理:从第一个元素或者最后一个元素开始遍历,逻辑就是根据得到的第一个元素节点顺藤摸瓜到最后一个元素节点,比较简单不累赘了,(可参考上篇文章arrayList的遍历原理一样,只不过链表是以元素向下寻找,有兴趣可自行跟进源码查看),遍历耗时很慢,每次查找都要从头开始,一般不采用。

好了LinkedList讨论到此结束,下篇文章讨论List集合下的Vector,每天进步一点点!!大家一起讨论学习,不足的地方还请指出来!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值