LinkedList

124 阅读3分钟

LinkedList没什么可说的,内部就是一个双向链表,涉及不到扩容,增删数据就是处理每个节点的前驱和后继指针指向的对象。这里只记一下get(index)和Iterator。

1、get(index)

public E get(int index) {
    checkElementIndex(index);  检验下标合法性,不合法抛出IndexOutOfBoundsException
    return node(index).item;
}

查询指定下标的元素
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方法中的if else作用是判断传入的下标是在链表的前半部分还是后半部分,如果是前半部分就从链表头部开始循环查找,否则就从尾部开始循环查找,这样能略微提升一点效率。可以看到获取指定下标元素的实现方式就是从头部(或者尾部)第一个元素开始for循环,直到循环到指定下标停止,此时的节点就是所需节点。由于查询指定下标需要把指定下标前面(或后面)的节点全部遍历一遍,所以效率偏低,查询的下标越靠近中部效率越低。

2、Iterator

LinkedList的iterator方法返回的是其内部类ListItr,如下:

private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;

    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }

    public boolean hasNext() {
        return nextIndex < size;
    }

    public E next() {
        checkForComodification();  检查 modCount,作用与ArrayList里一样,只不过在LinkedList里抽取成了方法
        if (!hasNext())
            throw new NoSuchElementException();

        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }

    public boolean hasPrevious() {
        return nextIndex > 0;
    }

    public E previous() {
        checkForComodification();  
        if (!hasPrevious())
            throw new NoSuchElementException();

        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }

    public int nextIndex() {
        return nextIndex;
    }

    public int previousIndex() {
        return nextIndex - 1;
    }

    public void remove() {
        checkForComodification();
        if (lastReturned == null)
            throw new IllegalStateException();

        Node<E> lastNext = lastReturned.next;
        unlink(lastReturned);
        if (next == lastReturned)
            next = lastNext;
        else
            nextIndex--;
        lastReturned = null;
        expectedModCount++;
    }

    public void set(E e) {
        if (lastReturned == null)
            throw new IllegalStateException();
        checkForComodification();
        lastReturned.item = e;
    }

    public void add(E e) {
        checkForComodification();
        lastReturned = null;
        if (next == null)
            linkLast(e);
        else
            linkBefore(e, next);
        nextIndex++;
        expectedModCount++;
    }

    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (modCount == expectedModCount && nextIndex < size) {
            action.accept(next.item);
            lastReturned = next;
            next = next.next;
            nextIndex++;
        }
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

可以看到实现原理很简单,next变量记录了当前节点,在构造方法中通过node方法获取到初始节点并赋值给next,后续通过next或者previous方法获取元素只是访问当前节点的前驱或后继节点,所以整个使用Iterator过程只需在初始化的时候调用一次node方法即可。

3、总结

主要理解为什么遍历LinkedList,使用Iterator比使用普通for循环效率高。因为使用普通for循环遍历时,每次循环都要调用一次linkedList.get方法,也就是node方法,上面讲了node方法比较耗时。而使用Iterator只需在初始化时调用了一次node方法,所以效率高。增强型for循环内部实现也是使用的Iterator,所以用增强型for循环遍历也比普通for循环效率高。

所以遍历LinkedList时除非需要使用index,否则一定不要使用普通for循环遍历,尤其在数据量大的时候,普通for循环遍历效率比Iterator效率差距非常巨大。