LinkedList

    ArrayList是通过数组实现存储,而LinkedList则是通过链表来存储数据,而且他实现的是一个双向链表,简单的说一下什么是双向链表。双向链表是数据结构的一种形式,他的每个节点维护两个指针,prev指向上一个节点,next指向下一个节点。这种结构有什么特点呢?他可以实现双向遍历,这使得在链表中的数据读取变得非常灵活自由。同时,LinkedList中维护了两个指针,一个指向头部,一个指向尾部。维护这两个指针后,可以使得元素从头部插入,也可以使元素从尾部插入。基于方式,用户很容易就能实现FIFO(队列),LIFO(栈)等效果。那么下面我们来看一下源码中的具体实现。

    1.Node节点定义:

    2.FIFO(队列)实现原理:
    队列的原理就是每次都从链表尾部添加元素,从链表头部获取元素,就像生活中的排队叫号,总是有个先来后到。

    1. // 队列尾部添加一个元素,建议使用这个,约定俗成吧。
    2. public boolean offer(E e) {
    3. return add(e);
    4. }
    5. // 队列尾部添加一个元素
    6. public boolean offerLast(E e) {
    7. addLast(e);
    8. return true;
    9. }
    10. // offer和offerLast底层调用的都是linkLast这个方法,顾名思义就是将元素添加到链表尾部。
    11. void linkLast(E e) {
    12. final Node<E> l = last;
    13. // 创建一个节点,将prev指针指向链表的尾节点。
    14. final Node<E> newNode = new Node<>(l, e, null);
    15. // 将last指针指向新创建的这个节点。
    16. last = newNode;
    17. if (l == null)
    18. // 如果当前链表为空,那么将头指针也指向这个节点。
    19. first = newNode;
    20. else
    21. // 将链表的尾节点的next指针指向新建的节点,这样就完整的实现了在链表尾部添加一个元素的功能。
    22. l.next = newNode;
    23. size++;
    24. modCount++;
    25. }
    26. // 在链表头部删除一个元素,建议用这个,别问我为什么,我也不知道
    27. public E poll() {
    28. return (f == null) ? null : unlinkFirst(f);
    29. }
    30. public E pollFirst() {
    31. final Node<E> f = first;
    32. return (f == null) ? null : unlinkFirst(f);
    33. }
    34. // poll和pollFirst底层调用的就是这个方法,将链表的头元素删除。
    35. private E unlinkFirst(Node<E> f) {
    36. // assert f == first && f != null;
    37. final E element = f.item;
    38. final Node<E> next = f.next;
    39. f.item = null;
    40. f.next = null; // help GC
    41. first = next;
    42. if (next == null)
    43. last = null;
    44. else
    45. next.prev = null;
    46. size--;
    47. modCount++;
    48. return element;
    49. }
    50. // 获取头元素,但是不会删除他。
    51. public E peek() {
    52. final Node<E> f = first;
    53. return (f == null) ? null : f.item;
    54. }

    以上就是LinkedList实现的两种功能,这里包含了大部分关于链表操作的方法,但不仅限于这几种。不管是栈也好,队列也好,元素都是从头部删除的unlinkFirst方法。但是用户在使用的过程中并不只用到上面两张方式,我们也可以从链表尾部删除元素如removeLast,peekLast,pollLast,unlinkLast等方法。

    上文讲到的功能,其实是实现了Deque接口,而现在要讲述的是实现与List的部分功能。那么最典型的操作就是直接对容器元素的读取,因为List容器的一大特点就是顺序存储,元素在容器中的位置和存入时是保持一致的,那么用户在读取元素的时候理所当然就可以通过元素下标来获取,下面就具体介绍这几种方法。

    1.将元素插入容器的指定位置

    1. // 将元素插入指定位置
    2. public void add(int index, E element) {
    3. checkPositionIndex(index);
    4. if (index == size)
    5. else
    6. // 如果不是位于末尾,那么就将元素插入指定位置的元素之前,
    7. linkBefore(element, node(index));
    8. }
    9. // 将元素插入指定元素前,链表插入元素是数据结构中最基础的知识,这里就不再赘述了。
    10. void linkBefore(E e, Node<E> succ) {
    11. // assert succ != null;
    12. final Node<E> pred = succ.prev;
    13. final Node<E> newNode = new Node<>(pred, e, succ);
    14. succ.prev = newNode;
    15. if (pred == null)
    16. first = newNode;
    17. else
    18. pred.next = newNode;
    19. size++;
    20. modCount++;
    21. }

    LinkedList的迭代器实现有两个,一个是实现了Iterator接口的DescendingIterator,另一个则是实现了ListIterator接口的ListItr。

    1.ListItr
    ListItr遍历需要指定一个起始值

    1. public ListIterator<E> listIterator(int index) {
    2. checkPositionIndex(index);
    3. return new ListItr(index);
    4. }

    ListItr会创建一个以index为起始值的迭代器,然后用户便可以以这个位置为起点,实现向前或者向后遍历。

    1. private class DescendingIterator implements Iterator<E> {
    2. private final ListItr itr = new ListItr(size());
    3. public boolean hasNext() {
    4. return itr.hasPrevious();
    5. }
    6. public E next() {
    7. return itr.previous();
    8. }
    9. public void remove() {
    10. itr.remove();