Java基础---LinkList

一. 描述

      双向链表(Doubly-linked)实现了接口List和Deque(双端队列),实现了所有的list操作并允许元素可为null。

        对于双向链表,所有的操作都符合预期。索引到列表的操作将从列表头或者列表尾开始遍历列表,以便更接近指定的索引为准。(不支持类似数组的随机访问,根据下标访问链表的话,需要从链表头或者链表尾开始遍历。)

  与ArrayList一样,此实现不是同步的。如果有多个线程访问同一个链表的话,并且至少上一个线程在结构上修改了链表,他必须对外同步。(结构修改是指r任何操作添加或者删除多个元素,仅仅设置元素值不是修改。)。这通常是在某些对象上的同步完成封装的。如果不存在这样的对象,则应该使用java.util. Collections.synchronizedList(new LinkedList(....))。

  快速失败,在ArrayList已经介绍了。

二. 结构

   LinkedList内部元素采用Node,链表来实现。代码如下,可以发现这是一个私有的内部类,导致我们无法直接获取Node,只能获取Node的值。

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

  这个就导致了一个很经典的面试问题,for循环遍历LinkedList为什么比遍历数组慢。原因:主要原因for循环遍历LinkedList我们无法获取Node类,无法获取节点属性的next(下一个节点),如果可以获取的话,那么我们从头部或者尾部遍历都是很快的。那么我们就只能通过下标去访问了,数组支持随机访问,根据下标访问的需要的时间复杂度O(1);而我们看下LinkedList的下标访问返回的是值,不是节点,我们无法获取到当前节点,也就无法获取下一个节点。

public E get(int index) {
checkElementIndex(index);
return node(index).item;
}

  接下来,我们继续查询获取节点的算法,我们发现根据下标来获取Node节点,需要从第一个节点或者最后一个节点遍历,直到到达下标的地方,这样时间复杂度最多需要O(2/n),由此可见for循环遍历效率比较低。那么迭代器为啥可以比较快呢,因为迭代器可以直接访问节点...还有下个节点,代码如下:

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

  那么结论来了:linkedList底层是链表实现,(1)下标随机访问链表,通过分析get方法,算法要求从头或者从尾依次访问节点,直到达到指定的节点,并且返回的是值而不是节点对象。(2)由于返回的是节点对象,我们无法直接获取节点对象的下一个对象地址,只能再通过get方法,再走一遍1的流程。(3)iterator可以访问节点,可以获取当前节点的下个节点,时间复杂度o(1)