LinkedList源码超详细解析

LinkedList源码分析

众所周知,LinkedList实现了List接口,LinkedList适用于集合元素先入先出和先入后出的场景,在队列的源码中被频繁使用,我们在使用一般的队列时,就直接可以拿LinkedList来实现,如下:

        LinkedList<Integer> queue = new LinkedList<>();
        queue.addLast(3);  //相当于入队操作
        queue.addLast(4);
        queue.addLast(5);
        System.out.println(queue.removeFirst());  //相当于出队操作(poll)
        System.out.println(queue.getFirst());   //相当于peek操作

可见LinkedList的操作十分的灵活!!

1. 整体架构

LinkedList的底层结构是一个双向链表,如下图:
在这里插入图片描述
看到上图的结构,注意到:

  • 当链表中没有数据的时候,first和last是同一个节点,前后指向都为零;
  • 双向链表大小只受内存大小的限制;

1. 类定义

先来看看这个类的定义:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

注意到了没,他还实现了Deque这个接口,所以LinkedList中还实现了poll()、peek()、push()、pop()等等,这些方法都是实现了Deque这个接口里的抽象方法,而这些方法的实现,就是基于LikedList里面本来的add、remove系列操作,比如看push()和pop()和peek()这三个方法:

    public void push(E e) {
        addFirst(e);   //头插法新增节点
    }
    public E pop() {  //删除头节点
        return removeFirst();
    }
    public E peek() {   //返回头节点
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

2. 底层结构

在LinedList里面有一个为Node的静态内部类:(这正是我们的底层结构)

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

3. 常用方法源码解析

1. 构造方法

有两个构造方法,一个是无参构造,一个是传入一个Collection集合,注意该集合的泛型得是当前定义的泛型的子类!

    public LinkedList() {
    }
	//该构造函数将传入的集合(符合要求的)转化为LinkedList
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

注意第二个构造函数:

  • 当传入的集合泛型不符合条件时,则会编译出错;

2. 新增操作

即为双向链表,所以新增节点的操作可以为头插,也可以是尾插;

在LikedList里面,定义了两个节点:

    transient Node<E> first;
    transient Node<E> last;

这两个初始值都为null;

1. add()方法(尾插)

add方法默认是从尾部开始增加节点的:

    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    void linkLast(E e) {
        //先获取尾节点
        final Node<E> l = last;
        //这里通过构造方法就将newNode的prev设置为last,next设置为空
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        //判断是不是空链表(插入前)
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        //记录更改
        modCount++;
    }
2. addFirst()方法 (头插)
    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)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

头插为尾插很相似,这里就没有加注释!

3. 删除操作

同样的,删除也分为头部删除和尾部删除

1. remove()方法 (也就是removeFirst方法)

remove方法实现的就是删除头节点,而具体实现查看源码可以发现就是调用了removeFirst:

    public E remove() {
        return removeFirst();
    }
    
    public E removeFirst() {
        //获取头节点
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    
    private E unlinkFirst(Node<E> f) {
        // 保存要删除的头节点的值
        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置空,因为一个节点的时候first和last都是这个节点;
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
2. 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;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

可以看出和removeFirst的逻辑是一样的;

3. remove(int index)方法(指定下标删除节点)⭐
    public E remove(int index) {
        //检查下标是否合法的一系列操作
        checkElementIndex(index);
        return unlink(node(index));
    }
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
	//根据下标查找到这个节点并且返回;
    Node<E> node(int index) {
        //如果index小于size的一半,说明这个节点在双向链表的左半侧,从first向右开始查找;
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            //此时index大于size的一半,说明这个节点在双向链表的右半侧,从last向左开始查找;
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return 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;
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            //如果要删除的节点后面为空的话,则该节点删除后,就应该把该节点前面的节点设置为last;
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

注意点:

  • 可以看出删除指定下标的节点的逻辑是:
    • 先通过 checkElementIndex(int index) 方法检查要删除的下标是否存在;
    • 然后通过node(int index) 方法找到要删除的下标那个节点;
    • 执行unlink(Node node) 方法进行节点删除;
      • 结合前面的来看:
        unlink代表删除指定节点,unlinkFirst代表删除头节点,unlinkLast代表删除尾节点!
  • 设计亮点:
    • node(int index)方法在查找节点的时候并不是从头开始遍历,而是通过了一次二分法,来确定是从头还是从尾开始遍历;

4. 节点查询

1. get(int index)方法:获取指定下标的节点值
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

上面已经分析过node这个方法,用来获取指定下标的节点,所以上面查询很简单;

2. indexOf(Object o)方法 :获取指定节点的值的下标
    public int indexOf(Object o) {
        int index = 0;
        if (o == 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))
                    return index;
                index++;
            }
        }
        return -1;
    }
3. contains(Object o)方法:是否存在某个节点值的节点⭐
public boolean contains(Object o) {
    return indexOf(o) != -1;
}

这里直接调用了上面的方法,很巧妙!!!

4. 迭代器

1. 单项迭代器

我们知道,在JDK1.5之前Iterable接口(注意不是Iterator接口,在Iterable接口里定义了获取Iterator的抽象方法)中的iterator()方法是直接在Collection接口中定义的,之后Collection接口就直接实现了Iterable接口 ,自然也就继承了iterator这个抽象方法,从而让自己的子类去完成实现 ,所以最原始的迭代器在每个集合里都可以直接调用iterator()方法去获取它的迭代器

LinkedList也一样,只不过注意iterator的具体实现不是在LinkedList里面完成的,而是它的父抽象类:AbstractSequentialList < E > ,而这个类又继承了AbstractList 类,具体的实现就是在这个类里面实现了迭代器的具体细节;

给上集合的一张类图继承关系:
在这里插入图片描述
所以LinkedList使用迭代器操作很简单:

        LinkedList<Integer> list = new LinkedList<>();
        Iterator<Integer> integerIterator =  list.iterator();
        while (integerIterator.hasNext()) {
            System.out.print(integerIterator.next()+" ");
        }

2. 双向迭代器 ⭐

在LinkedList中虽然没有直接实现单向迭代器,但实现了双向迭代器ListItr:
这个类是LinkedList的内部类,实现了ListIterator接口,而ListIterator接口实现了Iterator;
(ListIterator是专为双向迭代设计的一个接口,就是比Iterator多了关于从后往前遍历的抽象方法

    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;

        //这里得参数代表迭代的位置从哪开始,如果不填参数,则会调用父类AbstractList的迭代器
        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }

        public boolean hasNext() {
            return nextIndex < size;
        }

        public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

        public boolean hasPrevious() {
            return nextIndex > 0;
        }

        public E previous() {
            checkForComodification();
            if (!hasPrevious())
                throw new NoSuchElementException();

            lastReturned = next = (next == null) ? last : next.prev;
            nextIndex--;
            return lastReturned.item;
        }

        public int nextIndex() {
            return nextIndex;
        }

        public int previousIndex() {
            return nextIndex - 1;
        }

        public void remove() {
            checkForComodification();
            if (lastReturned == null)
                throw new IllegalStateException();

            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned);
            if (next == lastReturned)
                next = lastNext;
            else
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }

        public void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.item = e;
        }

        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null)
                linkLast(e);
            else
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            while (modCount == expectedModCount && nextIndex < size) {
                action.accept(next.item);
                lastReturned = next;
                next = next.next;
                nextIndex++;
            }
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

然后在LinkedList中提供了获取这个迭代器的方法:

public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

3. 迭代器源码分析⭐⭐⭐

1. 使用案例
        LinkedList<Integer> list = new LinkedList<>();
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);

        //使用双向迭代器从后往前迭代,这里参数得填,否则默认为零,那就从0号下标往前迭代,当然没元素;
        ListIterator<Integer> listIterator =  list.listIterator(2);
        while (listIterator.hasPrevious()) {
            System.out.print(listIterator.previous()+" ");
        }
        System.out.println();
        //使用双向迭代器从前往后迭代,这里也可以传参数,不传默认为零
        ListIterator<Integer> listIterator1 =  list.listIterator();
        while (listIterator1.hasNext()) {
            System.out.print(listIterator1.next()+" ");
        }

输出:
2 1
1 2 3

我们在LinkedList类中看到了自己实现的双向迭代器类ListItr这个类只有一个有参构造,需要传入一个整数,代表开始迭代的位置,那么上面怎么能调用无参的listIterator()方法呢?

原因是无参的那个方法不是LinkedList实现的是他的父类AbstractList实现的,我们来看看这个类:
在这里插入图片描述
我们的ArrayList和LinkedList都继承了这个抽象类,看它的类结构,里面封装了两个内部类:Itr和ListItr,看着两个名字就知道他们是用来实现迭代器的,而ListItr又继承了Itr,看这两个类中的方法:

  • Itr类中实现了基本的迭代,这个类实现了Iterator接口,没有双向迭代的功能;
    • 所谓基本迭代,进去发现其实就是默认位置从下标0开始,一次往后遍历来实现遍历元素;
  • ListItr类继承了Itr,所以它也拥有基本的迭代功能,它也实现了ListIterator接口,所以它能实现双向迭代功能,你看它的那几个方法嘛,比如previous()和hasPrevious();
    • ListItr构造方法也是必须要传入参数的,用来指定你从哪开始迭代,因为有了从后往前迭代,所以你得指定你从哪个下标开始嘛;

而在AbstractList类中,提供了iterator()和listIterator(int index)和listIterator(无参)这些方法来获取迭代器:

  • iterator()获取的就是上面的Itr这个类的对象;

        public Iterator<E> iterator() {
            return new Itr();
        }
    
    • 我们在LinkedList的源码中看到没有覆写iterator()这个方法,所以LinkedList获取普通迭代器就是使用的父类Abstract类中的iterator方法来获取的,而LinkedList实现了自己的双向迭代器;
    • 注意:ArrayList中自己即实现了自己的单向迭代器(iterator()方法),也自己实现了listIractor方法来提供自己的双向迭代器,所以ArrayList在使用迭代器的时候使用的是自己的,不是它的父类Abstract中的;
  • listIterator(int index)获取的就是上面ListItr类的对象:

        public ListIterator<E> listIterator(final int index) {
            rangeCheckForAdd(index);
            //上面说过,这个双向的迭代器必须传入参数
            return new ListItr(index);
        }
    
  • 而listIterator(无参)就是默认参数为零:

        public ListIterator<E> listIterator() {
            return listIterator(0);
        }
    

    这个方法正是上面那个问题的解!!LinkedList在使用listIterator却不带参数时,正是调用它的父类中的这个方法;

在这里插入图片描述

4. 总结⭐⭐

上面叙述得稍些杂乱,这里记住这几点:

  • LinkedList只实现了自己的有参双向迭代器(listIterator(int index)方法获取),若要使用普通迭代器和有参双向迭代器,都是调用父类AbstractList类的;
    • 即LinkedList中只有listIterator(int index)这个方法;
  • ArrayList都实现了,都是调用自己的;
    • 即ArrayList中,iterator(),listIterator()、listIterator(int index)这三个方法它都有;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章