一文颠覆你对 ArrayList 和 LinkedList 的认知

简介: 在初学Java的时候我们经常能看到这么一句话:ArrayList增删慢查询快,LinkedList增删快查询慢。但随着学习的不断深入,我们会发现这句话并不对,初学时看到这句话主要还是为了新手友好。

image.png


在初学Java的时候我们经常能看到这么一句话:ArrayList增删慢查询快,LinkedList增删快查询慢。但随着学习的不断深入,我们会发现这句话并不对,初学时看到这句话主要还是为了新手友好。


首先让我们了解一下为啥会有这个说法


ArrayList查询快:因为底层是数组,在查询的时候直接通过索引获取元素,所以查询快


ArrayList增删慢:往数组里某个位置增删元素的时候,是涉及元素的拷贝的,所以慢


LinkedList查询慢:要沿着链表上的节点慢慢找,当然慢


LinkedList增删快:只要断开改变链表中简单的改变节点指针的指向即可(如果对这里说的内容不太清楚,那你非常有必要好好学习一下数据结构)


嗯……,对于初学时来说听着简直太有道理了,感觉完全滴没有问题。终究还是当时太肤浅了。接下来看看具体哪些地方有问题。


到底是哪里不对呢?


以下就先来说说哪些地方不对。


ArrayList查询快,LinkedList查询慢


这一点比较简单,因为我们查询某一数据,我们根本就不会知道它那数据的索引,既然不知道索引,那ArrayList要查询某数据时一样得通过遍历去慢慢对比,这样的话就和 LinkedList 一样时间复杂度都为 O(N) 了。所以这句话是错的。


ArrayList 对所有位置元素的增删都要拷贝吗?


删除元素的时候必须传入索引,它的 remove 和 removeRange 方法都要传入索引,里面都用到了 System.arraycopy 这个方法


但是往最后末尾添加元素的时候,其实是不涉及拷贝的。


请看下面源码:


image.png


对于ArrayList来说,我们往里添元素的时候一般都是直接添,也就是直接添加到最后的,所以在我们使用的时候ArrayList的时候其实添加速度并不差。


简单的测试一下:


public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        test(arrayList);
        LinkedList<String> linkedList = new LinkedList<>();
        test(linkedList);
    }
    public static void test(List<String> list) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            list.add("哈哈哈");
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }


image.png


结果发现,往尾部添加元素的时候 ArrayList 甚至比 LinkedList 的还要快,而且在以上代码中,我是把 test(arrayList); 放在 test(linkedList); 前面的,而代码刚开始执行时,JIT即时编译器还没有对 test 方法的代码进行处理,是跑的更慢的,但即使在这种情况下还是要远远快于 LinkedList (如果还没有学过 JVM 不知道啥是 JIT,上面那段话你就先理解成:在前面执行,还没预热好,发挥不出来,所以对于刚开始执行的代码来说会慢一些)


既然我们知道往尾部添加元素的时候 ArrayList 是比 LinkedList 快的,那我们就想啊,以后如果是基本往尾部加元素的话就用 ArrayList ,往中间部分加就用 LinkedList 好了。但是事情可能没有这么简单,让我们再测测:


public static void main(String[] args) {
    ArrayList<String> arrayList = new ArrayList<>();
    test(arrayList);
    LinkedList<String> linkedList = new LinkedList<>();
    test(linkedList);
}
public static void test(List<String> list) {
    long start = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++) {
        list.add(list.size() / 2, "哈哈哈");
    }
    long end = System.currentTimeMillis();
    System.out.println(end - start);
}

这里我循环的次数比上一个测试多删了两个0,因为刚开始没删等了好久都没出结果


image.png


发现还是 ArrayList 完胜。离谱!!这 ArrayList 属实优点牛逼啊。


那这是为啥呢?(下面有一些是个人拙见,如有不对,希望大佬可以指教。)


对于 LinkedList 来说,它需要慢慢的沿着链表找到中间位置,再对相应节点(pre-cur-next 节点)指针进行调整,这个过程是比较费劲的。而且 LinkedList 还得把每个加入的数据包装成一个节点,相比于 ArrayList 来说也要麻烦一些


那 ArrayList 还要对添加位置前后的数据进行拷贝,那就不慢了吗?


确实是个问题,但是因为 ArrayList 底层是数组,它可以很好的配合 cpu 缓存 及 局部性原理来提高这个过程的速度。


  • cpu 缓存:cpu 缓存的容量比内存小的多但是交换速度却比内存要快得多。cpu 缓存的出现主要是为了解决cpu 运算速度与内存读写速度不匹配的矛盾,因为cpu 运算速度要比内存读写速度快很多,这样会使cpu 花费很长时间等待数据到来或把数据写入内存。
  • 局部性原理:当访问一块数据时,其相邻数据也很可能被访问,就一次性的把那一片的都读到 cpu 缓存中。


而链表就无法像数组那样利用这一点:就算会把你某个节点周围内存的数据一起读入,但你当前节点指向的空间可能并不是周围的数据,一起读进去的就不是你下次很可能用的数据了。所以它对于链表来说并无屁用。


而 ArrayList 正因为这个 cpu缓存+局部性原理 的帮助,使得数组拷贝的时候基本可以算是一大片一大片的拷贝的,所以速度非常地快。


到这里本文也就结束了,简单的总结一下:


  1. ArrayList 增删慢查询快,LinkedList增删快查询慢这句话可以说是不对的
  2. ArrayList 在很多方面都强于 LinkedList,即使是往中间部分加元素也是如此
目录
相关文章
|
2月前
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
69 3
|
2月前
|
存储 Java
Map大揭秘:HashMap与TreeMap背后的故事,你听过吗?
Map大揭秘:HashMap与TreeMap背后的故事,你听过吗?
26 1
|
6月前
|
Java 开发者
Queue大比拼:为何LinkedList能在众多Java集合中脱颖而出?
【6月更文挑战第18天】**Java的LinkedList作为队列的优势在于其双向链表实现,支持O(1)时间复杂度的首尾操作,适合作为Queue接口的实现。它也是线程不安全的,但在单线程环境下性能优越,并可通过Collections同步化。此外,它的灵活性使其也能胜任栈和双端队列的角色。**
47 5
|
6月前
|
存储 Java
深入Java List:探寻有序集合背后的故事
【6月更文挑战第17天】Java List接口,作为有序集合,用于数据存储与处理。ArrayList和LinkedList是常见实现类:ArrayList基于数组,适合随机访问但插入删除慢;LinkedList基于链表,插入删除快但随机访问效率低。在需要频繁在开头插入元素并高效访问时,应选用LinkedList。了解这些原理能帮助优化代码性能。
39 0
|
6月前
杨老师带你深入研究ArrayList和LinkedList的区别不同
杨老师带你深入研究ArrayList和LinkedList的区别不同
28 0
|
6月前
|
Java 索引
那些年,我们追过的Java List——ArrayList与LinkedList的爱恨情仇
【6月更文挑战第17天】ArrayList与LinkedList,Java List接口的双子星,各有千秋。ArrayList基于数组,随机访问快速,但插入删除慢;LinkedList用链表实现,插入删除高效,但索引访问慢。两者在爱恨情仇中教会我们权衡选择,成为编程旅程中难忘的记忆。 ```
27 0
|
6月前
|
算法 Java 数据处理
从HashSet到TreeSet,一场Java集合的“不重复”革命!
【6月更文挑战第17天】Java集合框架中的Set接口确保元素唯一,HashSet基于哈希表实现高效查找,不保证顺序;TreeSet使用红黑树保持排序,适用于有序场景。示例展示了HashSet的无重复添加及TreeSet的升序排列。Set是处理唯一性数据的利器。
40 0
|
7月前
|
存储 Java 索引
[AIGC] ArrayList介绍
[AIGC] ArrayList介绍
|
7月前
|
存储 安全 Java
Java集合巨头:深入ArrayList的底蕴
Java集合巨头:深入ArrayList的底蕴
55 0
Java集合巨头:深入ArrayList的底蕴
|
7月前
|
存储 算法 Java
ArrayList vs. LinkedList:数据结构之争
ArrayList vs. LinkedList:数据结构之争
65 0