JDK中LinkedList的实现分析

LinkedList

JDK中的LinkedList是继承自AbstractSequentialList,并实现了List、Deque、Queue等接口,并支持拷贝和序列化。

public class LinkedList<E> extends AbstractSequentialList<E> implements
    List<E>, Deque<E>, Queue<E>, Cloneable, Serializable 

先来阅读以下注释,了解一下概况:

LinkedList是List的一个实现,基于双向链表结构。所有的操作,包括添加,删除,元素替换都是支持的。
支持所有的元素,包括null。
这个类在你需要类似队列的行为的时候会用到。也会在你期望你的列表包含零个或者一个元素,但是又能扩展到更大数量的元素的时候。一般来说,你在不需要使用队列类似的功能的时候,最好使用ArrayList。

Link类是一个内部静态类,包括前驱和后继以及数据,实现了链表节点的功能。

LinkIterator是一个实现ListIterator接口的内部静态类,实现了链表的遍历功能。

ReverseLinkIterator是一个实现了Iterator接口的内部类,实现了逆序遍历的功能。

LinkedList使用一个叫做voidLink的空节点记录链表的头指针和尾指针,voidLink.previous是尾指针,voidLink.next是头指针。在没有结点时,链表的头尾都指向voidLink自身。

LinkedList包含以下几个主要操作的函数add、addAll、contains、get、indexOf、remove、push、pop、set等。下面一一来看看它们的实现。

首先是add,函数实现如下:

public void add(int location, E object) {
    if (location >= 0 && location <= size) {
        Link<E> link = voidLink;
        if (location < (size / 2)) {
            for (int i = 0; i <= location; i++) {
                link = link.next;
            }
        } else {
            for (int i = size; i > location; i--) {
                link = link.previous;
            }
        }
        Link<E> previous = link.previous;
        Link<E> newLink = new Link<E>(object, previous, link);
        previous.next = newLink;
        link.previous = newLink;
        size++;
        modCount++;
    } else {
        throw new IndexOutOfBoundsException();
    }
}

add函数可以实现在指定位置添加指定的对象数据。如果位置超出范围,会抛出IndexOutOfBoundsException异常。
如果插入的位置location在链表的前半部分,那么就从前面开始遍历到该位置;如果location在链表的后半部分,就从链表的尾部开始往前遍历到该位置。LinkedList之后通过该对象数据创建一个节点,并将节点插入到链表中,并更新前驱和后继结点指针,以及链表长度。

不带插入位置参数location的add函数则直接将对象数据插入到链表尾部。之后更新voidLink的链表的头尾指针。

addAll函数将一个Collection集合中的所有对象数据都插入到指定位置,实现过程与add函数基本一致,只不过是连续插入多个元素。

addFirst函数则是创建新结点并设置前驱后继,然后更新头指针,更新原来头指针的前驱,代码如下:

public void addFirst(E object) {
    addFirstImpl(object);
}

private boolean addFirstImpl(E object) {
    Link<E> oldFirst = voidLink.next;
    Link<E> newLink = new Link<E>(object, voidLink, oldFirst);
    voidLink.next = newLink;
    oldFirst.previous = newLink;
    size++;
    modCount++;
    return true;
}

addLast函数与addFirst函数实现类似。

contains函数用于搜索链表是否包含某个指定的对象。首先判断该对象是否为空,如果不为空,则从前向后遍历该链表进行元素对比查找,直到找到元素或者链表遍历完为止;如果元素为空,则遍历链表查找第一个为空的结点。

get函数用于返回指定位置的结点数据。如果位置在链表长度允许范围之内,则根据位置在前半部分或者后半部分的情况,从头指针往后或从尾指针往前进行查找。如果位置超出范围,抛出IndexOutOfBoundsException异常。

indexOf函数用于查找指定对象数据在链表中的位置。实现方法是从前往后遍历链表进行对象查找,直到找到对象数据或者遍历完链表。如果找到就返回位置,否则返回-1。
lastIndexOf函数与indexOf类似,但是不一样的是它返回的是最后一次出现的位置。因此,遍历方向需要变化,即变成从后往前进行链表的遍历。

remove函数用于删除指定位置的结点。先判断位置在前半部分还是后半部分来确定遍历方向,然后更新结点指针即可。另外该函数需要返回该位置的对象数据。如果位置超出范围,也会抛出IndexOutOfBoundsException异常。

public E remove(int location) {
    if (location >= 0 && location < size) {
        Link<E> link = voidLink;
        if (location < (size / 2)) {
            for (int i = 0; i <= location; i++) {
                link = link.next;
            }
        } else {
            for (int i = size; i > location; i--) {
                link = link.previous;
            }
        }
        Link<E> previous = link.previous;
        Link<E> next = link.next;
        previous.next = next;
        next.previous = previous;
        size--;
        modCount++;
        return link.data;
    }
    throw new IndexOutOfBoundsException();
}

另外pop与push是实现了栈的功能,这两个函数是通过删除队首元素和在队尾添加元素实现的。

set函数用于替换指定位置的结点中的对象数据。只需遍历到位置并替换数据即可。

    public E set(int location, E object) {
    if (location >= 0 && location < size) {
        Link<E> link = voidLink;
        if (location < (size / 2)) {
            for (int i = 0; i <= location; i++) {
                link = link.next;
            }
        } else {
            for (int i = size; i > location; i--) {
                link = link.previous;
            }
        }
        E result = link.data;
        link.data = object;
        return result;
    }
    throw new IndexOutOfBoundsException();
}
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页