Java集合(6)——源码剖析(3)——LinkedList源码剖析

本文详细剖析了Java中的LinkedList,包括其内部节点类、成员变量、构造函数、添加、删除元素的多种方式,以及节点查询和实现Queue、Deque接口的方法。LinkedList作为双端链表,适用于先入先出、先入后出的场景,但非线程安全。同时对比了LinkedList与ArrayList的区别,强调了LinkedList在插入删除操作上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.概述

2.源码剖析

2.1 节点类(LinkedList内部类)

‘2.2 成员变量

2.3 构造函数

2.4 添加元素

(1)从链表尾部添加元素

(2)从链表头部添加元素

(3)在指定节点的前后添加元素

(4)将集合插入链表

2.5 删除元素

(1)从头部删除节点

(2)从尾部删除元素

(3)删除指定节点

2.6 节点查询

2.7 实现Queue接口和Deque的方法对比

2.8 迭代器

3.ArrayList和LinkedList的区别

4.LinkedList常用方法测试


 

1.概述

  • LinkedList是一个实现了List接口和Deque接口的双端链表。

  • LinkedList底层的链表结构使它支持高效的插入和删除操作

  • 另外它实现了Deque接口,使得LinkedList类也具有队列的特性,

  • LinkedList适合集合中先入先出,先入后出的场景,在队列源码中频繁使用

  • LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法

图解:

上图代表了一个双向链表结构,链表中的每个节点都可以向前或者向后追溯:

  • 链表中每个节点叫Node,Node有prev属性,代表前一个节点的位置,next属性,代表后一个节点的位置
  • first是双向链表的头节点,它的前一个节点是null
  • last是双向链表的尾节点,它的后一个节点是null
  • 当链表中没有数据时,first和last是同一个节点,前后指向都是null
  • 因为是个双向链表,只要机器内存足够大,是没有大小限制的

2.源码剖析

2.1 节点类(LinkedList内部类)

上述的Node节点如下

    private static class Node<E> {
        E item;     //元素值
        LinkedList.Node<E> next;  //指向后继节点的引用
        LinkedList.Node<E> prev;  //指向前驱结点的引用
        
        //构造方法
        Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
  • 这个类是LinkedList的内部类,代表双端链表的节点Node。这个类有三个属性,分别是前驱节点,本节点的值,后继结点。

‘2.2 成员变量

public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    /**
     * 链表的长度
     */
    transient int size = 0;

    /**
     * 头节点
     */
    transient Node<E> first;

    /**
     * 尾节点
     */
    transient Node<E> last;

}

2.3 构造函数

空构造方法

public LinkedList() { }

用已有的集合创建链表的构造方法:

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

2.4 添加元素

追加节点时,我们可以选择追加到链表头部,还是追加到链表尾部,add 方法默认是从尾部开始追加,addFirst 方法是从头部开始追加,我们分别来看下几种不同的追加方式:

(1)从链表尾部添加元素

    /**
     * 添加元素,即默认的在末尾添加,添加成功返回true
     */
    public boolean add(E e) {
        //在末尾添加元素
        linkLast(e);
        return true;
    }
    /**
     * 在链表的末尾添加元素
     */
    public void addLast(E e) {
        linkLast(e);
    }
    /**
     * 在链表的末尾添加元素
     */
    void linkLast(E e) {
        //保存原来的尾节点
        final Node<E> l = last;
        //创建新的节点,让它的prev指向原来的尾节点,next指向空
        final Node<E> newNode = new Node<>(l, e, null);
        //这时尾节点就是新的节点
        last = newNode;
        //但是原来的链表为空,则头节点就是新的节点
        if (l == null)
            first = newNode;
        else
            //原来的链表不为空,则让原来尾节点的next指向新节点
            l.next = newNode;

        //链表大小 + 1
        size++;
        //LinkedList集合的操作次数 + 1
        modCount++;
    }
  • 可以发现add和addLast方法均是调用的内部默认权限的函数linkLast
  • add和addLast的区别就是add添加成功后有返回值true,而addLast没有返回值

(2)从链表头部添加元素

    /**
     * 在链表的头部添加元素
     */
    public void addFirst(E e) {
        linkFirst(e);
    }
    /**
     * 在链表的头部添加元素的真正实现
     */
    private void linkFirst(E e) {
        //在头部插入节点,所以先保存原始头结点
        final Node<E> f = first;
        //新建一个节点,让它的next指向原来的头结点
        final Node<E> newNode = new Node<>(null, e, f);
        //头节点为新节点
        first = newNode;

        if (f == null)
            //原来的头结点为null,即链表为空 first=last=null
            //此时由于有了新节点,尾节点就变为了新节点
            last = newNode;
        else
            //链表本身不为空
            //原来的头结点的prev指向新节点
            f.prev = newNode;
        //链表大小 + 1
        size++;
        //LinkedList集合的操作次数 + 1
        modCount++;
    }

(3)在指定节点的前后添加元素

    /**
     * 向指定索引处添加元素
     */
    public void add(int index, E element) {
        /**
         * 对索引进行范围检查
         *
         * checkPositionIndex专门用于确保索引可以为size,即在[0,size]
         *
         * 此处可以为size是因为为size时,是在尾部添加元素
         */

        checkPositionIndex(index);
        //在尾部添加员
        if (index == size)
            linkLast(element);
        else
            //在第index节点前插入该元素,则插入后,新节点的索引即为index
            linkBefore(element, node(index));
    }
    /**
     * 在指定节点前插入元素e
     */
    void linkBefore(E e, Node<E> succ) {
        /**
         * 此处没有判断succ是否为null,因为此方法仅在此类中使用,
         * 所以作者的使用会保证它不为null,所以不必要给外界抛出异常
         */
        //在链表的插入和删除中一般都要先保存对应位置的节点,如下
        //先用临时节点pred 保存succ的前驱结点
        final Node<E> pred = succ.prev;
        //新建一个结点,值为e,前驱节点为succ的前驱节点,后继节点为succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        //让succ的prev引用指向新创建的节点,即插入的节点
        succ.prev = newNode;
        //既然是在前面插入,我们就要考虑边界,即当前节点succ为头结点的情况,即pred=null
        if (pred == null)
            //succ为头节点,插入后,新的节点变为头节点
            first = newNode;
        else
            //不为头结点的话,将原来的succ的前驱节点的next引用指向新的节点
            pred.next = newNode;
        //链表大小 + 1
        size++;
        //LinkedList集合的操作次数 + 1
        modCount++;
    }

(4)将集合插入链表

    /**
     * 将集合插入链表尾部
     * 
     */
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
    /**
     * 将集合插入链表指定索引处
     * 
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        //1.检查index范围是否在size之内
        checkPositionIndex(index);

        //2.toArray()方法把集合的数据存到对象数组中
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        //3.得到插入位置的前驱节点和后继节点
        Node<E> pred, succ;
        //如果插入位置为尾部,前驱节点为last,后继节点为null
        if (index == size) {
            succ = null;
            pred = last;
        }
        //否则,调用node()方法得到后继节点,再得到前驱节点
        else {
            succ = node(index);
            pred = succ.prev;
        }

        // 4:遍历数据将数据插入
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //创建新节点
            Node<E> newNode = new Node<>(pred, e, null);
            //如果插入位置在链表头部
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }

        //如果插入位置在尾部,重置last节点
        if (succ == null) {
            last = pred;
        }
        //否则,将插入的链表与先前链表连接起来
        else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

上面可以看出addAll方法通常包括下面四个步骤:

  • 1.检查index范围是否在size之内
  • 2.toArray()方法把集合的数据存到对象数组中
  • 3.得到插入位置的前驱和后继节点
  • 4.遍历数据,将数据插入到指定位置

2.5 删除元素

(1)从头部删除节点

    /**
     * 从头删除节点 
     * 
     * f是链表头节点
     */
    private E unlinkFirst(Node<E> f) {
        // 拿出头节点的值,作为方法的返回值
        final E element = f.item;
        // 拿出头节点的下一个节点
        final Node<E> next = f.next;
        
        //帮助GC回收头节点
        f.item = null;
        f.next = null;
        
        // 头节点的下一个节点成为头节点
        first = next;
        //如果 next 为空,表明链表为空
        if (next == null)
            last = null;
        
        //链表不为空,头节点的前一个节点指向 null
        else
            next.prev = null;
        //链表大小-1
        size--;
        //操作LinkedList的次数+1
        modCount++;
        return element;
    }
    /**
     * 提供给用户调用的方法
     * 封装了对链表为空的判断
     * 
     * 内部最终还是调用unlinkFirst
     */
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

(2)从尾部删除元素

    /**
     * 从尾部删除节点
     *
     * l是链表尾节点
     */
    private E unlinkLast(Node<E> l) {
        //保存尾节点的值,作为方法的返回值
        final E element = l.item;
        //保存尾节点的前一个节点
        final Node<E> prev = l.prev;

        //帮助GC回收头节点
        l.item = null;
        l.prev = null; // help GC

        //尾节点的前一个节点成为尾节点
        last = prev;
        //如果 prev 为空,表明移除尾节点后链表为空
        if (prev == null)
            first = null;

        //链表不为空,尾节点的下一个节点指向null
        else
            prev.next = null;
        //链表大小-1
        size--;
        //操作LinkedList的次数+1
        modCount++;
        return element;
    }
    /**
     * 提供给用户调用的方法
     * 封装了对链表为空的判断
     *
     * 内部最终还是调用unlinkLast
     */
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

(3)删除指定节点

    /**
     * 删除指定节点
     * @param x
     * @return
     */
    E unlink(Node<E> x) {
        //保存节点x的元素值、前驱节点、后继节点
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        //x的prev为null,表明节点x是头节点,让头节点后移
        if (prev == null) {
            first = next;
            
        //x不是头节点,让前驱节点的next指针指向x的下一个节点,    
        } else {
            prev.next = next;
            //并把x的字段prev置为null,帮助垃圾回收
            x.prev = null;
        }

        //x的next为null,表明节点x是尾节点,让尾节点前移
        if (next == null) {
            last = prev;
        //x不是尾节点,让后继节点的prev指针指向x的前一个节点, 
        } else {
            next.prev = prev;
            //并把x的字段next置为null,帮助垃圾回收
            x.next = null;
        }
        //将节点x的元素值置空
        x.item = null;
        //链表大小-1
        size--;
        //操作链表的次数+1
        modCount++;
        //返回节点的元素值
        return element;
    }
    /**
     * 删除指定索引处的节点
     * @param index
     * @return
     */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
    /**
     * 删除指定元素值的第一个节点
     *
     */
    public boolean remove(Object o) {
        //如果传入的元素值为null
        if (o == null) {
            //从头开始遍历查找元素值为null的第一个节点,如果找到调用unlink删除该节点,然后返回true
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }

         //如果传入的元素值不为null    
        } else {
            //从头开始遍历查找元素值和传入元素值相等的第一个节点,如果找到调用unlink删除该节点,然后返回true
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        //如果没有找到该值,就返回false
        return false;
    }

2.6 节点查询

链表查询某一个节点是比较慢的,需要挨个循环查找才行,我们看看 LinkedList 的源码是如何寻找节点的?

    Node<E> node(int index) {
        /**
         * 同理,这里也未对index做检查,是因为此方法只供此类使用,作者确保它使用时没有超范围
         */
        /**
         * 此处使用了一个巧妙的技巧:
         *      判断index和(size/2)大小,缩小了一般的循环范围(size >> 1 是 size 除以 2 的意思)
         *          如果index在链表的前半部分,就从第一个节点开始循环找到它的位置
         *          如果index在链表的后半部分,就从最后一个节点开始循环找到它的位置
         */
        if (index < (size >> 1)) {
            //index在前半部分
            Node<E> x = first;
             // 直到 for 循环到 index 的前一个 node 停止
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            //index在后半部分
            Node<E> x = last;
            // 直到 for 循环到 index 的后一个 node 停止
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
  • 从源码中我们可以发现,LinkedList 并没有采用从头循环到尾的做法,而是采取了简单二分法,首先看看 index 是在链表的前半部分,还是后半部分。如果是前半部分,就从头开始寻找,反之亦然。通过这种方式,使循环的次数至少降低了一半,提高了查找性能

2.7 实现Queue接口和Deque的方法对比

LinkedList 实现了 Queue 接口,在新增、删除、查询等方面增加了很多新的方法,这些方法在平时特别容易混淆,在链表为空的情况下,返回值也不太一样

添加:

    /**
     * 与add相同
     */
    public boolean offer(E e) {
        return add(e);
    }

删除:

    /**
     * 与removeFirst相同
     * 
     */
    public E remove() {
        return removeFirst();
    }
    /**
     * 如果链表为空返回null
     * 否则调用unlinkFirst(f)来删除头节点
     * 
     */
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

查询:

    /**
     * 链表为空抛出异常
     * 否则返回头节点
     */
    public E element() {
        return getFirst();
    }


    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
    /**
     * 如果链表为空返回null
     * 否则返回头节点
     */
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

列表对比

方法含义返回异常返回特殊值底层实现
新增 add(e)和offer(e) 
删除remove()poll(e)链表为空时,remove会抛出异常,poll返回null
查找element(e)peek(e)链表为空时,element会抛出异常,peek返回null

 

 

 

 

 

同样,LinkedList 也实现了 Deque 接口,对新增、删除和查找都提供从头开始,还是从尾开始两种方向的方法

原理同上都是简单的封装,请直接阅读源码

2.8 迭代器

因为 LinkedList 要实现双向的迭代访问,所以我们使用 Iterator 接口肯定不行了,因为 Iterator 只支持从头到尾的访问。Java 新增了一个迭代接口,叫做:ListIterator,这个接口提供了向前和向后的迭代方法

LinkedList的迭代器ListItr实现了ListIterator

// 双向迭代器
private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;//上一次执行 next() 或者 previos() 方法时的节点位置
    private Node<E> next;//下一个节点
    private int nextIndex;//下一个节点的位置
    //expectedModCount:期望版本号;modCount:目前最新版本号
    private int expectedModCount = modCount;
    …………
}

从头到尾的迭代:

// 判断还有没有下一个元素
public boolean hasNext() {
    return nextIndex < size;// 下一个节点的索引小于链表的大小,就有
}

// 取下一个元素
public E next() {
    //检查期望版本号有无发生变化
    checkForComodification();
    if (!hasNext())//再次检查
        throw new NoSuchElementException();
    // next 是当前节点,在上一次执行 next() 方法时被赋值的。
    // 第一次执行时,是在初始化迭代器的时候,next 被赋值的
    lastReturned = next;
    // next 是下一个节点了,为下次迭代做准备
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}

从尾到头的迭代:

// 如果上次节点索引位置大于 0,就还有节点可以迭代
public boolean hasPrevious() {
    return nextIndex > 0;
}
// 取前一个节点
public E previous() {
    checkForComodification();
    if (!hasPrevious())
        throw new NoSuchElementException();
    // next 为空场景:1:说明是第一次迭代,取尾节点(last);2:上一次操作把尾节点删除掉了
    // next 不为空场景:说明已经发生过迭代了,直接取前一个节点即可(next.prev)
    lastReturned = next = (next == null) ? last : next.prev;
    // 索引位置变化
    nextIndex--;
    return lastReturned.item;
}

迭代器的删除:

LinkedList删除的时候,推荐通过迭代器删除:

public void remove() {
    checkForComodification();
    // lastReturned 是本次迭代需要删除的值,分以下空和非空两种情况:
    // lastReturned 为空,说明调用者没有主动执行过 next() 或者 previos(),直接报错
    // lastReturned 不为空,是在上次执行 next() 或者 previos()方法时赋的值
    if (lastReturned == null)
        throw new IllegalStateException();
    Node<E> lastNext = lastReturned.next;
    //删除当前节点
    unlink(lastReturned);
    // next == lastReturned 的场景分析:从尾到头递归顺序,并且是第一次迭代,并且要删除最后一个元素的情况下
    // 这种情况下,previous() 方法里面设置了 lastReturned = next = last,所以 next 和 lastReturned会相等
    if (next == lastReturned)
        // 这时候 lastReturned 是尾节点,lastNext 是 null,所以 next 也是 null,这样在 previous() 执行时,
        //发现 next 是 null,就会把尾节点赋值给 next
        next = lastNext;
    else
        nextIndex--;
    lastReturned = null;
    expectedModCount++;
}

3.ArrayList和LinkedList的区别

  • 1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

  • 2. 底层数据结构: Arraylist 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)

  • 3. 插入和删除是否受元素位置的影响: 

    • ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。

    • ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。

  • 4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。

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

4.LinkedList常用方法测试

此段代码来自:https://snailclimb.top/JavaGuide/#/java/collection/LinkedList

package LinkedList;

import java.util.Iterator;
import java.util.LinkedList;

public class LinkedListDemo01 {
    public static void main(String[] srgs) {
        //创建存放int类型的linkedList
        LinkedList<Integer> linkedList = new LinkedList<>();
        /************************** linkedList的基本操作 ************************/
        linkedList.addFirst(0); // 添加元素到列表开头
        linkedList.add(1); // 在列表结尾添加元素
        linkedList.add(2, 2); // 在指定位置添加元素
        linkedList.addLast(3); // 添加元素到列表结尾

        System.out.println("LinkedList(直接输出的): " + linkedList);

        System.out.println("getFirst()获得第一个元素: " + linkedList.getFirst()); // 返回此列表的第一个元素
        System.out.println("getLast()获得第最后一个元素: " + linkedList.getLast()); // 返回此列表的最后一个元素
        System.out.println("removeFirst()删除第一个元素并返回: " + linkedList.removeFirst()); // 移除并返回此列表的第一个元素
        System.out.println("removeLast()删除最后一个元素并返回: " + linkedList.removeLast()); // 移除并返回此列表的最后一个元素
        System.out.println("After remove:" + linkedList);
        System.out.println("contains()方法判断列表是否包含1这个元素:" + linkedList.contains(1)); // 判断此列表包含指定元素,如果是,则返回true
        System.out.println("该linkedList的大小 : " + linkedList.size()); // 返回此列表的元素个数

        /************************** 位置访问操作 ************************/
        System.out.println("-----------------------------------------");
        linkedList.set(1, 3); // 将此列表中指定位置的元素替换为指定的元素
        System.out.println("After set(1, 3):" + linkedList);
        System.out.println("get(1)获得指定位置(这里为1)的元素: " + linkedList.get(1)); // 返回此列表中指定位置处的元素

        /************************** Search操作 ************************/
        System.out.println("-----------------------------------------");
        linkedList.add(3);
        System.out.println("indexOf(3): " + linkedList.indexOf(3)); // 返回此列表中首次出现的指定元素的索引
        System.out.println("lastIndexOf(3): " + linkedList.lastIndexOf(3));// 返回此列表中最后出现的指定元素的索引

        /************************** Queue操作 ************************/
        System.out.println("-----------------------------------------");
        System.out.println("peek(): " + linkedList.peek()); // 获取但不移除此列表的头
        System.out.println("element(): " + linkedList.element()); // 获取但不移除此列表的头
        linkedList.poll(); // 获取并移除此列表的头
        System.out.println("After poll():" + linkedList);
        linkedList.remove();
        System.out.println("After remove():" + linkedList); // 获取并移除此列表的头
        linkedList.offer(4);
        System.out.println("After offer(4):" + linkedList); // 将指定元素添加到此列表的末尾

        /************************** Deque操作 ************************/
        System.out.println("-----------------------------------------");
        linkedList.offerFirst(2); // 在此列表的开头插入指定的元素
        System.out.println("After offerFirst(2):" + linkedList);
        linkedList.offerLast(5); // 在此列表末尾插入指定的元素
        System.out.println("After offerLast(5):" + linkedList);
        System.out.println("peekFirst(): " + linkedList.peekFirst()); // 获取但不移除此列表的第一个元素
        System.out.println("peekLast(): " + linkedList.peekLast()); // 获取但不移除此列表的第一个元素
        linkedList.pollFirst(); // 获取并移除此列表的第一个元素
        System.out.println("After pollFirst():" + linkedList);
        linkedList.pollLast(); // 获取并移除此列表的最后一个元素
        System.out.println("After pollLast():" + linkedList);
        linkedList.push(2); // 将元素推入此列表所表示的堆栈(插入到列表的头)
        System.out.println("After push(2):" + linkedList);
        linkedList.pop(); // 从此列表所表示的堆栈处弹出一个元素(获取并移除列表第一个元素)
        System.out.println("After pop():" + linkedList);
        linkedList.add(3);
        linkedList.removeFirstOccurrence(3); // 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表)
        System.out.println("After removeFirstOccurrence(3):" + linkedList);
        linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从尾部到头部遍历列表)
        System.out.println("After removeLastOccurrence(3):" + linkedList);

        /************************** 遍历操作 ************************/
        System.out.println("-----------------------------------------");
        linkedList.clear();
        for (int i = 0; i < 100000; i++) {
            linkedList.add(i);
        }
        // 迭代器遍历
        long start = System.currentTimeMillis();
        Iterator<Integer> iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            iterator.next();
        }
        long end = System.currentTimeMillis();
        System.out.println("Iterator:" + (end - start) + " ms");

        // 顺序遍历(随机遍历)
        start = System.currentTimeMillis();
        for (int i = 0; i < linkedList.size(); i++) {
            linkedList.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("for:" + (end - start) + " ms");

        // 另一种for循环遍历
        start = System.currentTimeMillis();
        for (Integer i : linkedList)
            ;
        end = System.currentTimeMillis();
        System.out.println("for2:" + (end - start) + " ms");

        // 通过pollFirst()或pollLast()来遍历LinkedList
        LinkedList<Integer> temp1 = new LinkedList<>();
        temp1.addAll(linkedList);
        start = System.currentTimeMillis();
        while (temp1.size() != 0) {
            temp1.pollFirst();
        }
        end = System.currentTimeMillis();
        System.out.println("pollFirst()或pollLast():" + (end - start) + " ms");

        // 通过removeFirst()或removeLast()来遍历LinkedList
        LinkedList<Integer> temp2 = new LinkedList<>();
        temp2.addAll(linkedList);
        start = System.currentTimeMillis();
        while (temp2.size() != 0) {
            temp2.removeFirst();
        }
        end = System.currentTimeMillis();
        System.out.println("removeFirst()或removeLast():" + (end - start) + " ms");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值