Java八股文系列二:集合之LinkedList

200 阅读3分钟

image

一:LinkedList源码分析

1.1 成员变量

    //长度
    transient int size = 0;

    //第一个节点
    transient Node<E> first;

    //最后一个节点
    transient Node<E> 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;
        }
    }

1.2 构造方法

    //无参构造方法
    public LinkedList() {
    }

    //使用指定 Collection 来构造 ArrayList
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

1.3 add操作

在链表末尾添加元素:

    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

先把e的前驱节点置为当前链表的最后一个节点,后置节点置为null;然后判断最后一个节点是否为null,如果为null则把e当作第一个节点,否则最后一个节点的next指向e。

在指定位置添加元素:

    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    
    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;
        }
    }
    
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

先找到index位置的节点,把e的前驱节点设置为index节点的前驱节点,把e的后置节点设置为index位置的节点。

1.4 get操作

    public E get(int index) {
        checkElementIndex(index);
        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;
        }
    }

如果index小于链表长度的一半,则从头节点开始往后遍历,否则从尾节点往前遍历。

1.5 remove操作

删除指定位置的元素:

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
    
    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;
    }

index节点的前驱节点prev,prev节点的后置节点设置为index节点的后置节点next,断开index节点的前驱指针;index的后置节点next,next节点的前驱节点设置为index节点的前驱节点prev,断开index节点的后置指针,index节点的值设置为null。

删除元素:

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

从头结点遍历链表,比较节点的值是否相等,相等的话删除。

1.6 pop、poll、push、peek、offer

unlinkFirst(断开第一个节点)linkFirst(成为第一个节点)linkLast(成为最后一个节点)getFirst(得到第一个节点的值)
pop() 如果第一个是null的话会报错push()offer(E e)peek()
poll() 如果第一个是null就返回null
remove() 如果第一个是null的话会报错

1.7 遍历

    public static void main(String[] args) {
        LinkedList<Integer> list = getLinkedList();
        //通过快速随机访问遍历LinkedList
        listByNormalFor(list);
        //通过增强for循环遍历LinkedList
        listByStrongThenFor(list);
        //通过快迭代器遍历LinkedList
        listByIterator(list);
    }

    /**
     * 构建一个LinkedList集合,包含元素50000个
     * @return
     */
    private static LinkedList<Integer> getLinkedList() {
        LinkedList list = new LinkedList();
        for (int i = 0; i < 50000; i++){
            list.add(i);
        }
        return list;
    }
  • 普通for循环
    /**
     * 通过快速随机访问遍历LinkedList
     */
    private static void listByNormalFor(LinkedList<Integer> list) {
        // 记录开始时间
        long start = System.currentTimeMillis();
        int size = list.size();
        for (int i = 0; i < size; i++) {
            list.get(i);
        }
        // 记录用时
        long interval = System.currentTimeMillis() - start;
        System.out.println("listByNormalFor:" + interval + " ms");
    }
  • 增强型for循环
    /**
     * 通过增强for循环遍历LinkedList
     * @param list
     */
    public static void listByStrongThenFor(LinkedList<Integer> list){
        // 记录开始时间
        long start = System.currentTimeMillis();
        for (Integer i : list) { }
        // 记录用时
        long interval = System.currentTimeMillis() - start;
        System.out.println("listByStrongThenFor:" + interval + " ms");
    }
  • Iterator迭代器
    /**
     * 通过快迭代器遍历LinkedList
     */
    private static void listByIterator(LinkedList<Integer> list) {
        // 记录开始时间
        long start = System.currentTimeMillis();
        for(Iterator iter = list.iterator(); iter.hasNext();) {
            iter.next();
        }
        // 记录用时
        long interval = System.currentTimeMillis() - start;
        System.out.println("listByIterator:" + interval + " ms");
    }

用时对比:

listByNormalFor:1042 ms
listByStrongThenFor:8 ms
listByIterator:4 ms

普通for循环的用时远大于迭代器的遍历。

二、LinkedList常见面试问题

2.1 LinkedList和ArrayList的区别?

  • ArrayList是数组,内存连续;LinkedList是节点,内存不连续。
  • 正是因为内存连续,所以ArrayList查找的时间复杂度是O(1),插入删除的时间复杂度是O(n);LinkedList查找的时间复杂度是O(n),插入删除的时间复杂度是O(1)。

2.2 如何用LinkedList实现堆栈和队列的数据结构?

public class MyStack {

    private LinkedList list;

    public MyStack() {
        this.list = new LinkedList();
    }

    public void push(Object o) {
        this.list.addLast(o);
    }

    public Object pop() {
        //堆栈
        return this.list.removeLast();
        
        //队列
        //return this.list.removeFirst();
    }

    public boolean isEmpty() {
        return this.list.isEmpty();
    }

}

2.3 为什么用普通for循环遍历比迭代器要慢?

因为普通for循环每一次循环都要从头节点开始遍历,而迭代器每取一个元素就将游标指向下一个节点,根据游标可以直接得到下一个元素。

三、总结

  • LinkedList是双向链表,内存地址不连续,查找的时间复杂度是O(n),插入删除的时间复杂度是O(1)。
  • 尽量不使用普通for循环遍历LinkedList。