JDK源码阅读笔记之LinkedList

java Nov 27, 2018

前言

对大部分需求来说,ArrayList就能满足我们的需求,但是LinkedList和ArrayList的区别是什么,他内部的实现是什么样的,需要我们去搞清楚,才能对两者进行恰当的使用。

LinkedList的概述:

  1. LinkedList本质上是一个双向列表,虽然他没有这么叫。他不仅仅实现了List接口,同时还实现了Deque接口。
  2. LinkedList允许null元素。
  3. LinkedList是线程不安全的,在多线程环境下修改LinkedList的结构要在外部加锁。
  4. 可以使用Collections的synchronizedList方法来包装LinkedList,实现线程安全性。
  5. LinedList对插入删除操作的时间复杂度是O(1),但是对查找非首尾的时间复杂度是O(n)。

LinkedList的属性:

LinkedList一共有三个属性:

  1. size:表示LinkedList的当前大小
  2. first:表示头节点
  3. last:表示尾节点

LinkedList的方法:

LinkedList的所有public方法如下图,下面我们来通过源码解释来了解他们

构造方法:

  1. LinkedList():
  public LinkedList() {
    }

这个构造方法初始化了一个空的LinkedList对象。
2. LinkedList(Collection<? extends E>)

  public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

通过内部的addAll方法将传入的集合对象的元素加入到LinkList中,addAll方法的时间复杂度是O(n)。详情请看addAll方法。

其他方法:

1.add(E e)

  public boolean add(E e) {
        linkLast(e);
        return true;
    }

add方法用来向LinkedList中添加元素,接受一个范型的参数,通过默认的linkLast方法对元素e进行操作。

 void linkLast(E e) {
        final Node<E> l = last; //  保存之前的last元素
        final Node<E> newNode = new Node<>(l, e, null); // 创建新的节点,他的前置节点是之前的last节点
        last = newNode; // 将新节点赋给last节点
        if (l == null)  // 如果之前的last节点是null 说明是一个空LinkedList
            first = newNode; // 这时候头节点也应该指向新节点(整个LinkedList中只有一个元素)
        else
            l.next = newNode; // 不是空LinkedList,将之前的last节点节点的后置节点变为新节点
        size++; // 整个LinkedList的大小加1
        modCount++; // 操作标示加1
    }

2.add(int index, E element):

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

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

将一个元素插入到指定的位置。首先会对插入的校验是否合法,然后如果是插入的最后位置,调用linkLast方法(详情见构造方法2),其他位置先调用node方法,获取对应位置的节点,然后调用linkBefore方法插入。

 private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
  private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }    
    

checkPositionIndex方法index是不是大于0小于LinkedList的size。不是的话,就会抛出IndexOutOfBoundsException异常。

 Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) { // 判断index是否小于size / 2。使用右移一位提高计算效率。
            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;
        }
    }

node方法对节点进行查找。用到了二分查找的思想来节约时间,小于size/2从头开始查找,大于size/2从尾开始查找。

 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) // 前置节点是null,为空LinkedList 
            first = newNode; // 头节点指向新节点
        else
            pred.next = newNode; // 前置节点下一个元素指向新节点
        size++; // size + 1
        modCount++; // 操作标示 + 1
    }

前置插入代码如上。

3.addAll(Collection<? extends E> c)

 public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

addAll(Collection<? extends E> c)是addAll(int index, Collection<? extends E> c)方法的重载方法。当在末尾插入元素的时候就等同addAll(Collection<? extends E> c)方法。

4.addAll(int index, Collection<? extends E> c):

public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index); // 检查是否越界

        Object[] a = c.toArray();  //把插入集合转成数组 如果集合c是null,那么就会触发NullPointerException
        int numNew = a.length; 
        if (numNew == 0) // 没有元素可以插入 return failse。未变更LinkedList
            return false;

        Node<E> pred, succ; // 声明前置和后置节点
        if (index == size) { // 如果插入位置是末尾 前置元素就是last,后置元素是null
            succ = null;
            pred = last;
        } else {
            succ = node(index); // 中间插入,首先找到插入位置节点,赋值给succ。前置节点赋值给pred。
            pred = succ.prev;
        }
        //开始循环插入
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null); // 初始化新节点,前置节点设为pred
            if (pred == null) // 空LinkedList 头节点也设为新节点
                first = newNode;
            else
                pred.next = newNode; // pred后置节点关联新节点
            pred = newNode; // pred设为新节点,继续下一轮循环。
        }

        if (succ == null) { // 空LinkedList或从最后一个节点插入, 尾节点也设为插入的最后节点
            last = pred;
        } else {
            pred.next = succ; // 插入的最后节点的后置节点和succ互相关联
            succ.prev = pred; 
        }

        size += numNew; // size增加
        modCount++; // 操作标示增加
        return true; 
    }

指定位置插入整个集合到LinkedList中,将插入位置和后面的元素全部右移,时间复杂度是O(n)。

5.addFirst(E e)


    public void addFirst(E e) {
        linkFirst(e);
    }

    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f); // 初始化新节点,并将后置节点设为头节点
        first = newNode; // 新节点变为头节点
        if (f == null) // 旧头节点是null,LinkedList是空
            last = newNode; // 尾节点也设为新节点
        else
            f.prev = newNode; // 旧头节点的前置节点设为新节点
        size++; 
        modCount++;
    }

就是将新元素插入到LinkedList头部。内部调用的是linkFirst方法来实现插入逻辑。

6.addLast(E e)

addLast方法将元素插入到LinkedList最后,方法等同于add(E e)。详细代码见add方法。

7.clear()

 public void clear() {
        // Clearing all of the links between nodes is "unnecessary", but:
        // - helps a generational GC if the discarded nodes inhabit
        //   more than one generation
        // - is sure to free memory even if there is a reachable Iterator
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }

将LinkedList里面的元素清空。逻辑上就是将所有的引用都变为null。下次gc的时候就被回收了。时间复杂度是O(n)

8.clone()

 public Object clone() {
        LinkedList<E> clone = superClone();

        // Put clone into "virgin" state
        clone.first = clone.last = null;
        clone.size = 0;
        clone.modCount = 0;

        // Initialize clone with our elements
        for (Node<E> x = first; x != null; x = x.next)
            clone.add(x.item);

        return clone;
    }
    
    private LinkedList<E> superClone() {
        try {
            return (LinkedList<E>) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }
    

对LinkedList进行克隆。从代码可以看出只是对LinkedList本身进行了克隆。但是元素没有进行克隆。也就是浅拷贝,在平时使用的时候如果想要元素也进行复制的话,需要手动进行深拷贝。

9.contains(Object o)

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) { // 如果o是null,那么只要从头开头遍历是否有null元素就可以了
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) // o不为null,要通过o的equals方法来判断是否存在这个元素。
                    return index;
                index++;
            }
        }
        return -1; // 找不到返回索引为-1
    }
    

判断方法是否包含某个元素。通过indexOf方法判断是否存在查找元素的索引来实现。indexOf方法的代码如上。

10. descendingIterator()

 public Iterator<E> descendingIterator() {
        return new DescendingIterator();
    }
      private class DescendingIterator implements Iterator<E> {
        private final ListItr itr = new ListItr(size());
        public boolean hasNext() {
            return itr.hasPrevious();
        }
        public E next() {
            return itr.previous();
        }
        public void remove() {
            itr.remove();
        }
    }
    

提供逆序遍历的Iterator。如上代码所示,正序遍历元素的时候通过ListItr的next方法,DescendingIterator的next方法是ListItr的previous方法。

11. element()

    public E element() {
        return getFirst();
    }
    
 

只是返回第一个元素,不会将第一个元素从LinkedList中删除。如果LinkedList为empty(first元素是null)会抛出NoSuchElementException。

12. get()

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

获取对应节点的元素,首先校验获取的节点是否合法。如果合法,通过node方法检索取出对应的节点元素。

13. getFirst()


  public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

等同于element方法。

14. getLast()

  public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

获取LinkedList尾节点元素,如果

15. indexOf(Object o)

代码见上面,indexOf就是找到索引元素第一次出现在LinkedList中的位置。如果没有找到返回-1。

16. lastIndexOf(Object o)

 public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

返回索引元素最后一次出现在LinkedList中的位置。如果没有找到返回-1。和indexOf相反的是从最后一个元素开始查找。

17.offer(E e)

public boolean offer(E e) {
        return add(e);
    }

将元素e添加到LinkedList的末尾。等同于调用add方法。

18. offerFirst(E e)

 public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

在LinkedList头部添加元素。等同于调用addFirst方法。

19. offerLast(E e)

  public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

再LinkedList的尾部添加元素。等同于调用addLast方法。因为addLast方法等同于add方法,add方法等同于offer方法,所以offerLast等同于offer方法。

20. peek()

  public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

返回第一个元素,但是不会从LinkedList里面将他移除。如果LinkedList为empty,返回null。

21.  peekFirst()

public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

22. peekLast()

public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

返回LinkedList中的最后一个元素。如果LinkedList为empty,返回null。

23. poll()

public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(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; // 将first节点置为next节点。
        if (next == null)
            last = null;
        else
            next.prev = null; // next节点的前置节点置为null。
        size--; // size减一
        modCount++; // 操作数加一
        return element;
}    
    

poll方法会返回并将第一个元素从LinkedList中删除。

24. pollFirst()

 public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

pollFirst逻辑上等同于poll方法。

25. pollLast()

    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }
    
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev; // 将last的prev节点赋给last
        if (prev == null)
            first = null;
        else
            prev.next = null; // 将last的prev节点的后置节点变为null
        size--; // size减一
        modCount++; // 操作数加一
        return element;
    }

pollLast方法会返回并将最后一个元素从LinkedList中删除。

26. pop()

  public E pop() {
        return removeFirst();
    }
   public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }    
    

功能等价于平时使用的堆栈的pop,将栈顶(LinkedList)的最上面的元素返回并从堆栈(LinkedList)中删除。逻辑上等价于poll,本质上是调用了removeFirst方法。如果是LinkedList是empty,会抛出NoSuchElementException异常。

27. push()

  public void push(E e) {
        addFirst(e);
    }

对应堆栈的push方法。将新元素压入栈顶。本质上是调用了addFirst方法。

28. remove()

  public E remove() {
        return removeFirst();
    }

类似pop,poll方法返回第一个元素,同时将第一个元素从LinkedList中删除。

29. remove(int index)

  public E remove(int index) {
        checkElementIndex(index); // 检查index是否合法
        return unlink(node(index)); // 将index处节点从LinkedList中删除
    }
    
     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的前置节点的后置指针变为next。这样就将x从节点中移除,将x的prev和next节点关联起来。
            x.prev = null; // x的前置节点变为null
        }

        if (next == null) { 
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null; // 帮助gc,删除x节点元素
        size--;
        modCount++;
        return element;
    }
    

在特定位置移除元素。

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

将第一个出现的元素o从LinkedList中移除。

31.removeFirst()

代码见pop方法。将LinkedList中第一个元素移除。

32.removeLast()

  public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;  // 将last节点置为l节点的前置节点
        if (prev == null)
            first = null;
        else
            prev.next = null; 
        size--;
        modCount++;
        return element;
    }
    

将最后一个元素从LinkedList中移除。

33. removeFirstOccurrence(Object o)

   public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

又是一个相同的方法,可能是为了让语义上更容易理解吧。调用的是remove(Object o)方法。将第一次出现的特定元素从LinkedList中删除。

34. removeLastOccurrence(Object o)

 public boolean removeLastOccurrence(Object o) {
        if (o == null) { // 如果为null ,直接查找最后出现的null元素
            for (Node<E> x = last; x != null; x = x.prev) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

将最后出现的元素从LinkedList中删除。

35. set(int index, E element)

   public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

检查index是否和发,通过node方法找出对应节点。将节点的元素置换为新值。

36. toArray()

 public Object[] toArray() {
        Object[] result = new Object[size]; // 内部创建一个数组result
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item; // 将所有元素加到result内
        return result;
    }

将LinkedList的数据转化为Array类型。方法的时间复杂度和空间复杂度都是O(n)级别的。

37. toArray(T[] a)

 public <T> T[] toArray(T[] a) {
        if (a.length < size)
            a = (T[])java.lang.reflect.Array.newInstance(
                                a.getClass().getComponentType(), size); // a的空间不够就创建一个新数组
        int i = 0;
        Object[] result = a;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item; // 添加元素

        if (a.length > size) // a的空间超过LinkedList的size就将size处元素置为null
            a[size] = null;

        return a;
    }


将LinkedList的元素替换到传入的数组a中。

总结:

了解了LinkedList,很多地方以后不能瞎用。最后的toArray方法中Array.newInstance以前都不知道,有时间好好研究下。下次介绍Vector类。

zzx

There is my place for writing,coding and reading