LinkedList 解析

357 阅读5分钟
知乎:zhuanlan.zhihu.com/p/77749905

官方对LinkedList的介绍

Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null).实现所有可选的列表操作,并允许所有的元素包括 null.

All of the operations perform as could be expected for a doubly-linked list.Operations that index into the list will traverse the list from the beginning or the end, whichever is closer to the specified index.所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端).

Note that this implementation is not synchronized.LinkedList是不同步的 If multiple threads access a linked list concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list:

List list = Collections.synchronizedList(new LinkedList(...));

This class is a member of the Java Collections Framework.

(以上摘自JDK1.7)

自己总结一下:LinkedList以双向链表实现,它是线程不安全的。除了实现List接口外,LinkedList还为在列表的开头及结尾get、remove和insert元素提供了统一的命名方法。这些操作可以将链接列表当作栈,队列和双端队列来使用。LinkedList的iterator和listIterator方法返回的迭代器是快速失败的(fail-fast机制):在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的remove或add方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。

LinkedList源码

transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

首先是三个属性,size以及Node类型的first和last,而Node就是它数据存储的基本单位,first用来记录当前第一个,last则是记录最后一个

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

查看Node,它是一个内部类,其中的item就是我们要操作的数据,而next和prev就是用来记录当前这个元素它的上一个和下一个是谁

add(E e)

// 查看add方法,调用的linkLast(),我们看下这个方法做了些什么
public boolean add(E e) {
  linkLast(e);
  return true;
}

void linkLast(E e) {
  // last我们在上面有讲到就是当前的最后一个元素(如果是首次调用add方法,那么则为null),把它  赋值给l
  final Node<E> l = last;
  // 新建一个Node,其中e是我们要添加的元素,l是之前的最后一个元素
  final Node<E> newNode = new Node<>(l, e, null);
  // 此时我们新添加的元素就是当前的最后一个元素
  last = newNode;
  // 判断l是否为null,如果是说明第一次添加元素,那么新添加的元素也是first,如果不是,那么,l的next就是我们新添加的元素
  if (l == null)
    first = newNode;
  else
    l.next = newNode;
  // 当前的size加一
  size++;
    // 操作次数加一
  modCount++;
}

LinkedList内部声明了Node类型的first和last属性,Node是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点,也正是它其中的next和prev体现了它双向的说法。双向的特点决定了LinkedList的特点,LinkedList相对于ArrayList来说,是可以快速添加、删除元素,因为它只需要修改要插入位置前一个Node的next和后一个Node的prev指向自己。但同样的它查找的效率和ArrayList相比就要差很多,它没有下标来标志每个元素的位置。

我们看下在LinkedList中如何查找元素

get(int index)

public E get(int index) {
  // 先判断是否越界
  checkElementIndex(index);
  return node(index).item;
}

Node<E> node(int index) {
  // assert isElementIndex(index);
  // 先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果                index>size/2,就只从位置size往前遍历到位置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;
  }
}

remove(Object o)

public boolean remove(Object o) {
  // 首先判断要移除的元素是否为null,然后遍历整个链表找到指定元素后调用unlink方法,我们下面看下这个方法
  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;
}

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
    first = next;
  } else {
    // 如果不是,将它前一个元素的next指向当前的next,并将自己的至为null
    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的next和prve。在我们的实际开发中接合LinkedList的特点,在我们对一个集合有频繁的插入和删除操作时,LinkedList是更好的选择,它的性能要比ArrayList要好不用额外的去移动和拷贝元素,而如果只是单纯的查询ArrayaList则更加适合。