Java学习笔记——LinkedList插入和删除真的比ArrayList快吗

探讨了LinkedList和ArrayList在不同情况下的性能表现,特别是在插入和删除操作上的差异。通过代码实验,揭示了LinkedList在接近列表尾部进行操作时的优化策略。

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

Java学习笔记——LinkedList插入和删除速度真的比ArrayList快吗

  • 问:LinkedListArrayList 有什么区别?
  • 答:
  • LinkedList 实现了 ListDeque 接口,一般称为双向链表;
  • ArrayList 实现了List 接口,称为动态数组;
  • LinkedList 在插入和删除数据时效率更高;
  • ArrayList 在查找某个 index 的数据时效率更高;
  • LinkedListArrayList 需要更多的内存;

第二个区别的确没问题,但是少了条件,在某些条件下该结论是不成立的,先来看一个例子:

public class CollectionTest {

    public static long addTime(List<Integer> list) {
        long start = System.currentTimeMillis(); // 起始时间
        for (int i = 0; i < 50000; ++i) {
            list.add(1); // ArrayList和LinkedList的add(element e)方法都是向末尾追加元素
        }
        long end = System.currentTimeMillis(); // 终止时间
        return end - start;
    }

    public static void main(String[] args) {
        System.out.println("ArrayList(add):" + addTime(new ArrayList<>()) + "ms"); // 测试ArrayList
        System.out.println("LinkedList(add):" + addTime(new LinkedList<>()) + "ms"); // 测试LinkedList
    }
}

插入50000个数据,运行结果为:

ArrayList(get):3ms
LinkedList(get):2ms

问题不大,时间消耗差不多,因为时间复杂度都为O(1),看看它们的源码:
ArrayList.add(E e)源码:

/*
    ArrayList源码
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!! 就是数组长度要+1
    elementData[size++] = e; // 这就是向末尾追加一个元素,显然时间复杂度为O(1)
    return true;
}

LinkedList.add(E e)源码:

/*
    LinkedList源码
 */
public boolean add(E e) {
    linkLast(e); // 向末尾追加元素
    return true;
}

void linkLast(E e) {
    final Node<E> l = last; // last是list的末尾结点,由LinkedList类维护着
    final Node<E> newNode = new Node<>(l, e, null); // Node的创建格式   ->   Node<E>(前一个结点,数据,后一个结点),这里的含义是newNode的上一个结点是l(last),后面结点为空,数据为e
    last = newNode; // 让last重新指向newNode,表示newNode是新的末尾结点
    if (l == null)
        first = newNode; // 如果l为空,即list为空,插入newNode后当然first = last = newNode
    else
        l.next = newNode; // l的下一个结点指向newNode
    size++;
    modCount++;
}

将上述例子中的add方法改为向指定位置添加元素,并事先在list里面添加50000个元素:

public class CollectionTest {

    public static long addTime(List<Integer> list) {
        // 先让list里面有点东西,不然下面的add(i,1)就会变成在末尾添加元素,看不到效果
        for (int i = 0; i < 50000; ++i) { 
            list.add(1);
        }
        long start = System.currentTimeMillis(); // 起始时间
        for (int i = 0; i < 50000; ++i) {
            list.add(i, 1); // 注意此处的玄机
        }
        long end = System.currentTimeMillis(); // 终止时间
        return end - start;
    }

    public static void main(String[] args) {
        System.out.println("ArrayList(get):" + addTime(new ArrayList<>()) + "ms");
        System.out.println("LinkedList(get):" + addTime(new LinkedList<>()) + "ms");
    }
}

可以猜一猜结果,不出意外,ArrayList会快很多,结果如下:

ArrayList(get):583ms
LinkedList(get):2118ms

究其原因,是因为i值越大,对LinkedList越不利,因为LinkedList需要遍历找到i这个位置,然后再插入值,这个代价是很大的,而ArrayListi的位置是很快的,所以i越大,LinkedList的插入速度会越来越慢,而ArrayList的速度不会变化太大。

但是,i再大也有一定的上界的,我们可以将i设置为接近list.size() - 1,不要等于它,否则就是在末尾添加了,导致时间复杂度为O(1),这里我们设置为list.size() - 10

public class CollectionTest {

    public static long addTime(List<Integer> list) {
        // 先让list里面有点东西,不然下面的add(i,1)就会变成在末尾添加元素,看不到效果
        for (int i = 0; i < 50000; ++i) {
            list.add(1);
        }
        long start = System.currentTimeMillis(); // 起始时间
        for (int i = 0; i < 50000; ++i) {
            list.add(list.size() - 10, 1); // 注意此处的玄机
        }
        long end = System.currentTimeMillis(); // 终止时间
        return end - start;
    }

    public static void main(String[] args) {
        System.out.println("ArrayList(get):" + addTime(new ArrayList<>()) + "ms");
        System.out.println("LinkedList(get):" + addTime(new LinkedList<>()) + "ms");
    }
}

结果:

ArrayList(get):3ms
LinkedList(get):3ms

啊偶,不是说i越大速度越慢吗,怎么又变快了,但是如果把i变成list.size() - 100或者list.size() - 1000,你会发现,又变慢了,百思不得其解,究其源码,发现奥秘:

/*
    LinkedList源码
 */
public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index)); // 注意此处的node(index)方法
}

Node<E> node(int index) { // 找到索引为index的结点
    // assert isElementIndex(index);

    if (index < (size >> 1)) { // 如果index小于size/2,就从first开始遍历,即从头往后找
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {    // 如果index大于等于size/2,就从last开始遍历,即从尾向前找
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

原来是因为LinkedList优化了找指定结点的方式,靠近末尾结点就从末尾开始找,靠近开头就从开头开始找,这样就可以只遍历一半结点,这样我们很容易知道,当位置为list的中间时候将会是最费时的(需要遍历完一半),如下代码LinkedList的插入速度将会很慢:

public class CollectionTest {

    public static long addTime(List<Integer> list) {
        // 先让list里面有点东西,不然下面的add(i,1)就会变成在末尾添加元素,看不到效果
        for (int i = 0; i < 50000; ++i) {
            list.add(1);
        }
        long start = System.currentTimeMillis(); // 起始时间
        for (int i = 0; i < 50000; ++i) {
            list.add(list.size() / 2, 1); // 注意此处的玄机
        }
        long end = System.currentTimeMillis(); // 终止时间
        return end - start;
    }

    public static void main(String[] args) {
        System.out.println("ArrayList(get):" + addTime(new ArrayList<>()) + "ms");
        System.out.println("LinkedList(get):" + addTime(new LinkedList<>()) + "ms");
    }
}

结果(根本不是一个级别的,你还敢说LinkedListArrayList快吗):

ArrayList(get):442ms
LinkedList(get):3629ms

同理,remove方法是一样的,当index的位置越靠中点,LinkedList会越慢:

public class CollectionTest {

    public static long addTime(List<Integer> list) {
        // 先让list里面有点东西,不然下面的add(i,1)就会变成在末尾添加元素,看不到效果
        for (int i = 0; i < 50000; ++i) {
            list.add(1);
        }
        long start = System.currentTimeMillis(); // 起始时间
        for (int i = 0; i < 50000; ++i) {
            list.remove(list.size() / 2); // index为0会非常快
        }
        long end = System.currentTimeMillis(); // 终止时间
        return end - start;
    }

    public static void main(String[] args) {
        System.out.println("ArrayList(remove):" + addTime(new ArrayList<>()) + "ms");
        System.out.println("LinkedList(remove):" + addTime(new LinkedList<>()) + "ms");
    }
}

结果(根本不是一个级别的,你还敢说LinkedListArrayList快吗):

ArrayList(remove):210ms
LinkedList(remove):1428ms
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值