Java之LinkedList源码分析(第六篇:删除元素-List接口)

(注意:本文基于JDK1.8) 

 

remove(int)方法分析

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

用于删除指定下标处(位置)的元素对象,传入的第1个参数表示元素的位置,注意:第一个元素的位置是0

1、检查位置是否符合要求

首先将传入的index值传入checkElementIndex()方法中,checkElementIndex()方法将检查下标值index的范围是否越界,如果越界就会抛出著名的IndexOutOfBoundsException异常

2、获取即将删除元素所在的结点对象

只有下标index符合范围的情况下,才会将index值先传入到node()方法中,node()方法负责查找并返回指定下标index的Node对象

3、将找到的Node对象传入的unlink()方法中

该Node对象会被再次传入unlink()方法中,unlink()方法会执行删除Node对象操作

4、返回删除后的元素对象

remove()方法会返回被删除的Node对象持有的item(元素对象)

 

checkElementIndex()方法

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

用于检查元素下标范围的方法(前面文章中已经分析过,这里当作复习,是同一个方法)

1、传入的下标值index先被传入到isElementIndex()方法中,isElementIndex的返回true则代表下标index符合当前LinkedList对象持有的范围,该方法会直接结束

2、若isElementIndex()方法返回false,则说明下标index(位置)不满足LinkedList对象当前的范围,执行throw语句,此处会抛出一个IndexOutOfBoundsException对象,IndexOutOfBoundsException对象在创建时,则先调用outOfBoundsMsg()方法,该方法返回的字符串对象的内容用于提示调用者抛出异常的原因

 

node()方法分析

    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            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;
        }
    }

用于获取指定下标处(位置)的Node对象(前面文章已经分析过此方法,这里当作复习,为同一个方法)传入一个int参数表示位置

1、size是当前LinkedList对象持有的元素总数,先对size右移1位,这样的值刚好是size除以2,将传入的下标与size除以2的值进行比较,如果index小于size/2,说明index位于线性表的上半部分,就从头结点开始查找,如果index超过size/2的值,就从尾结点开始查找,这么做是为了更快的找到指定下标的结点对象

2、从头结点开始查找的过程

首先获得LinkedList对象持有的头结点first,由局部变量x负责保存,从头结点开始循环,当局部变量i的值等于下标index值时,循环结束,此时局部变量x保存的就是下标index对应的Node对象,return语句则会直接将Node对象返回给调用方

3、从尾结点开始查找的过程

首先获得LinkedList对象持有的尾结点last,由局部变量x保管,局部变量i则是从size-1的值为初始值,size-1是尾结点的下标值,每轮循环局部变量i的值都会减去1,且将当前Node对象持有的上一个Node对象的引用再由局部变量x保存,当局部变量i等于下标index时,循环结束,此时x持有的Node对象,就是对应下标index的Node对象,直接return返回给调用方法

 

unlink()方法分析

    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

实际进行删除Node的方法,传入一个Node对象,该方法负责删除元素(断开结点)并返回元素对象

双向链表的删除元素过程分析:

1、获取被删除Node结点持有的元素对象:将传入的Node对象x持有的元素对象item交给局部变量element负责持有

2、获取被删除Node结点指向的前驱与后继结点:将传入的Node对象x持有的next(后继结点)由局部变量next保存、再将传入的Node对象x持有的prev由局部变量prev保管

3、改变被删除Node结点的前驱结点的指向:当局部变量prev为null,说明被删除的结点是头结点,此时将first直接指向被删除Node结点指向的下一个结点next即可,若prev不为null,则需要改变被删除Node结点的前驱结点prev的next指向被删除Node结点指向的下一个结点next,同时被删除Node对象x持有的prev也需要置为null

4、改变被删除Node结点的后继结点的指向:当局部变量next为null,说明被删除的结点是尾结点(最后一个结点),此时将last直接指向被删除Node结点指向的上一个结点prev即可,若next不为null,则需要改变next结点的prev指向被删除Node结点的上一个结点prev即可,同时被删除结点对象x持有的next也需要置为null

5、将被删除Node结点持有的元素对象置为空

6、更新元素总数size减去1,modCount增加1,最后返回元素对象element

 

isElementIndex()方法分析

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

用于检查下标值index(位置)是否在有效范围

当下标index大于等于0时,且小于当前元素总数size时,返回true代表index符合范围,返回false代表index超出范围界限(注意这个方法没有index == size的判断)

 

outOfBoundsMsg()方法分析

    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

传入的下标值index(位置)用于拼接字符串,组合的字符串会被返回用于提示用户异常产生的原因是什么……

拼接(过程:Index + 下标值,Size:+ 元素总数

举例:Index:5,Size:4

 

remove(Object)方法分析

    public boolean remove(Object o) {
        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;
    }

用于删除匹配元素的方法,传入的Object对象表示元素对象

LinkedList对象可持有null元素,针对null与非null元素分开处理,因无法获悉元素对象的位置,所以都是从头结点开始遍历查找元素

1、null元素的移除:当传入的o为null,说明要移除的元素是null,首先获得LinkedList对象持有的头结点Node对象first,然后由局部变量x保管,从头结点开始,一直遍历,直到当前局部变量x持有的为null时停止遍历(最后一个结点持有的next为null),遍历的每轮都会做一件事,获得当前Node对象x持有的item,判断item是否指向null,当item为null时,将当前Node对象x传入unlink方法中(见3号知识点),unlink方法将实际移除匹配的结点对象(结点对象持有的元素也随之会被移除),最后方法返回true

2、非null元素的移除:同样从头结点开始遍历,因为是非null的元素,所以用了Object下的equals方法进行对比,相等的元素,也会将匹配的当前结点对象x传入到unlink方法中(见3号知识点),最后也会返回true

3、当null元素或非null元素,均没有找到匹配项,整个remove方法会返回false,代表删除元素失败

 

总结

1、LinkedList移除元素,本质是移除双向链表中的结点对象(Node对象),每个元素对象是由一个Node对象持有的实例变量item负责保存,所以移除了Node对象,也就移除了元素对象

2、针对指定下标(位置)移除方法,作者根据index处于size/2的上半部分或者下半部分,采用从头部开始或者从尾部开始的遍历方式查找结点,目的是为了更快找的找到结点对象

3、针对匹配元素对象的移除方法,只能从头结点开始遍历查找(因为不知道距离头结点或者尾结点谁更近),并且针对null元素与非元素分别进行了处理

4、modCount值在移除元素的方法中也进行了更新,防止多线程下使用LinkedList

下一篇将继续分析与Deque接口相关的移除元素方法

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值