LinkedList中查询(contains)和删除(remove)源码分析

一、contains源码分析

本文分析双向链表LinkedList的查询操作源码实现。jdk中源程序中,LinkedList的查询操作,通过contains(Object o)函数实现。具体见下面两部分程序:

public boolean contains(Object o) {
    return indexOf(o) != -1;
}

public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

indexOf函数查询对象o在链表中的索引位置。

源码首先将元素为null的情形单独判读剥离,个人分析,应该是如果不单独分析,null元素在下边的for循环中,不能执行o.equals操作(编译器输入null.,会自动将已有输入替换为NullPointerException.,提示空指针异常)。

由于链表不同于数组,在内存中并非连续存储,因此访问某个元素需要遍历。源程序中使用for循环进行遍历。index表示链表元素索引,初值为0。

1、针对空元素(null)的情况,用for循环遍历,查找元素为null的节点,并返回索引index。for循环初始条件为头指针(无元素),判断条件为节点不为null,自增表达式为x重赋值为下个节点(利用节点x的next指针实现,在java中next为node对象的属性成员)。每次自增,index+1。
2、针对非空元素,遍历操作同上。函数结束的判断条件变为o.equals(x.item),这里equals方法为Object超类的方法,程序中元素类型非Object也可调用。

两种情形下,链表遍历完毕(仍为return),表明该元素o在链表中不存在,因此返回-1。

二、remove源码对比

通过查阅,发现remove方法和contains方法的源码实现非常相似,列出如下:

/**
 * Removes the first occurrence of the specified element from this list, 
 * if it is present.  If this list does not contain the element, it is
 * unchanged. 
 * @param o element to be removed from this list, if present
 * @return {@code true} if this list contained the specified element
 */

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

对比发现,和contains类似,remove操作首先也是查找Object o是否存在与表中。空节点元素和非空节点的循环判断基本相同,在找到Object o后,区别与contains的返回索引,remove需要删除节点link(LinkedList的删除需要对next链进行重配置引用)。

删除节点的方法unlink的源码如下:

//Unlinks non-null node x. 
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++;  //注:已从结构上修改此列表的次数。AbstractList属性
    return element;
}

分析:
删除节点程序中,需要考虑节点在链表中的不同位置,进行不同的操作。

1、节点于链表首端

LinkedList中查询(contains)和删除(remove)源码分析

此时,特殊操作是需要将first指针(引用)指向其后的节点。

first = next;

2、节点于链表尾端

LinkedList中查询(contains)和删除(remove)源码分析

此时,需要将上一个节点的next指针指向null,即上个节点称为尾节点。

last = prev;

3、节点于链表中间情况

LinkedList中查询(contains)和删除(remove)源码分析

如果不仔细思考,我们在写程序时,会直接在两个if判断后,将剩余的相关操作写在一起。而源码并没有这么做,稍作分析,就能看出,如果前一个if中,条件是prev,因此可对该节点的prev进行相关操作。而后续的if语句,还需判断该节点的next引用,因此在第一个if-else语句体中,不对next进行操作,否则更改后,后学的next已经不是该节点的原next值。

因此,第一个if-else中:

prev.next = next;
x.prev = null;

第二个if-else中:

next.prev = prev;
x.next = null;

三、总结

从LinkedList的源码可以看到,源程序中相应方法代码简洁,逻辑清晰正确,在学习java的过程中,除了学习知识为我所用,也要避免闭门造车,多查看源码和其他优秀的项目程序,参考优秀的编程习惯和思想,从而积累经验,提高自己的水平。

相关推荐