一、概要

  • LinkedList 不是线程安全的;
  • LinkedList 底层使用双向链表来存储数据;
  • 可以添加任意元素,包括 null;
  • LinkedList 实现了双向链表和双端队列特点(维护了两个属性 first 和 last 分别指向首节点和尾节点);
  • 每个节点(Node 对象),里面又维护了 prev、next、item 三个属性,其中通过 prev 指向前一个,通过 next 指向后一个节点,最终实现双向链表;
  • 因为 LinkedList 底层使用链表进行存储,因此元素的添加和删除相对来说效率较高。

模拟双向链表

  1. public class Main {
  2. public static void main(String[] args) {
  3. Node node = new Node(5);
  4. Node node1 = new Node("798string");
  5. Node node2 = new Node('C');
  6. Node node3 = new Node(true);
  7. // 连接四个节点,形成双向链表
  8. node.next = node1;
  9. node1.next = node2;
  10. node2.next = node3;
  11. node3.prev = node2;
  12. node2.prev = node1;
  13. node1.prev = node;
  14. // 双向链表的头尾节点
  15. Node first = node;
  16. Node last = node3;
  17. // 从头到尾遍历
  18. while (first != null) {
  19. System.out.println(first.item);
  20. first = first.next;
  21. }
  22. // 从尾到头遍历
  23. while (last != null) {
  24. System.out.println(last.item);
  25. last = last.prev;
  26. }
  27. }
  28. }
  29. class Node {
  30. public Object item;
  31. public Node prev;
  32. public Node next;
  33. public Node(Object item) {
  34. this.item = item;
  35. }
  36. public Node(Object item, Node prev, Node next) {
  37. this.item = item;
  38. this.prev = prev;
  39. this.next = next;
  40. }
  41. }

常用操作例子

  1. public class Main {
  2. public static void main(String[] args) {
  3. LinkedList linkedList = new LinkedList();
  4. // add
  5. linkedList.add("asd");
  6. linkedList.add('A');
  7. linkedList.add(123);
  8. linkedList.add(123.1F);
  9. linkedList.add(1231L);
  10. linkedList.add(null);
  11. linkedList.add(false);
  12. // add / insert
  13. linkedList.add(2, "eeeeeeee");
  14. linkedList.add(4, "aaaaaaaaa");
  15. System.out.println(linkedList);
  16. // remove
  17. linkedList.remove();
  18. linkedList.remove(1);
  19. System.out.println(linkedList);
  20. // get
  21. System.out.println(linkedList.get(2));
  22. // 增强 for 遍历
  23. for (Object o : linkedList) {
  24. System.out.println(o);
  25. }
  26. // for 遍历
  27. for (int i = 0; i < linkedList.size(); i++) {
  28. System.out.println(linkedList.get(i));
  29. }
  30. }
  31. }

源码分析

1、构造器

image.png

  1. // 空参构造器
  2. public LinkedList() {
  3. }
  4. // 传入一个 Collection 初始化链表
  5. public LinkedList(Collection<? extends E> c) {
  6. this();
  7. addAll(c);
  8. }

2、add

挑两个比较典型的 add 方法做分析
image.png

add ( E e )

  1. // 将指定的元素添加到这个列表的末尾。该方法等同于addLast。
  2. public boolean add(E e) {
  3. linkLast(e);
  4. return true;
  5. }
  6. // 链接 e 作为链表的最后一个元素
  7. void linkLast(E e) {
  8. final Node<E> l = last;
  9. final Node<E> newNode = new Node<>(l, e, null);
  10. last = newNode;
  11. if (l == null)
  12. first = newNode;
  13. else
  14. l.next = newNode;
  15. size++;
  16. modCount++;
  17. }

add( int index, E element )

  1. public void add(int index, E element) {
  2. checkPositionIndex(index);
  3. if (index == size)
  4. linkLast(element);
  5. else
  6. linkBefore(element, node(index));
  7. }
  8. // 检查 index 是否合法
  9. private void checkPositionIndex(int index) {
  10. if (!isPositionIndex(index))
  11. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  12. }
  13. // 链接 e 作为链表的最后一个元素
  14. void linkLast(E e) {
  15. final Node<E> l = last;
  16. final Node<E> newNode = new Node<>(l, e, null);
  17. last = newNode;
  18. if (l == null)
  19. first = newNode;
  20. else
  21. l.next = newNode;
  22. size++;
  23. modCount++;
  24. }
  25. // 在非空节点 succ 之前插入元素 e。
  26. void linkBefore(E e, Node<E> succ) {
  27. // assert succ != null;
  28. final Node<E> pred = succ.prev;
  29. final Node<E> newNode = new Node<>(pred, e, succ);
  30. succ.prev = newNode;
  31. if (pred == null)
  32. first = newNode;
  33. else
  34. pred.next = newNode;
  35. size++;
  36. modCount++;
  37. }

3、remove

image.png

remove()

// 检索并删除该列表的头部(第一个元素)
public E remove() {
    return removeFirst();
}

// 移除并返回该列表中的第一个元素
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

// 解除非空的第一个节点f的链接
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

remove( int index )

// 移除该列表中指定位置的元素。将任何后续的元素向左移动(从它们的索引中减去1)。返回从列表中被移除的元素。
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

// 判断参数 index 是否是一个现有元素的索引。
private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

// 返回指定元素索引处的(非空)Node。
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;
    }
}

// 解除非空节点 x 的链接。
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 = next;
    } else {
        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;
}

remove( Object o )

public boolean remove(Object o) {
    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;
}

面试题

ArrayList 和 LinkedList 有什么区别?

1)底层数据结构

ArrayList 底层使⽤的是 Object 数组; LinkedList 底层使⽤的是双向链表数据结构。(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别。)

2)是否支持快速随机访问

LinkedList 不⽀持⾼效的随机元素访问,⽽ArrayList⽀持。快速随机访问就是通过元素的序号快速获取元素对象(对应于 get(int index) ⽅法)。

3)内存空间占用

ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留⼀定的容量空间,⽽ LinkedList 的空间花费则体现在它的每⼀个元素都需要消耗⽐ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。