ArrayList和LinkedList的区别(底层的数据结构、随机访问元素的性能、插入元素和删除元素的性能、内存占用、使用场景)

文章目录

  • [0. 前言](#0. 前言)
  • [1. 底层的数据结构](#1. 底层的数据结构)
  • [2. 随机访问元素的性能](#2. 随机访问元素的性能)
  • [3. 插入元素和删除元素的性能](#3. 插入元素和删除元素的性能)
  • [4. 内存占用](#4. 内存占用)
  • [5. 使用场景](#5. 使用场景)
  • [6. 频繁地进行插入操作,ArrayList和LinkedList哪个性能更好](#6. 频繁地进行插入操作,ArrayList和LinkedList哪个性能更好)
    • [6.1 在列表头部频繁地进行插入操作](#6.1 在列表头部频繁地进行插入操作)
    • [6.2 在列表尾部频繁地进行插入操作](#6.2 在列表尾部频繁地进行插入操作)
    • [6.3 在列表的随机位置频繁地进行插入操作](#6.3 在列表的随机位置频繁地进行插入操作)
  • [7. 扩展:影响ArrayList和LinkedList性能的因素](#7. 扩展:影响ArrayList和LinkedList性能的因素)

0. 前言

ArrayList 和 LinkedList 的区别,本质上就是数组和链表的区别,相信学过数据结构的同学都知道数组和链表的区别

如果是细问 ArrayList 和 LinkedList 在某个方面上的区别,大部分同学都能够答出来

但如果问的是 ArrayList 和 LinkedList 的区别,很多同学就答得很差了,可能会东扯一段,西扯一段,甚至还会出现说到某一部分的时候,发现上一部分说得又有点问题,又继续补充上一部分的情况

说到底,之所以回答得不是很好,是因为缺少了归纳总结这一环节,今天带大家一起总结一下 ArrayList 和 LinkedList 的区别

1. 底层的数据结构

  • ArrayList 底层采用数组来存储元素
  • LinkedList 底层采用双向链表来存储元素

2. 随机访问元素的性能

  • ArrayList 在随机访问元素上更高效,因为 ArrayList 可以根据下标来计算元素在数组中的位置
  • LinkedList 在随机访问元素上性能较差,因为 LinkedList 需要从头部或尾部开始遍历链表,找到元素在链表中的位置

我们来测试一下 ArrayList 和 LinkedList 在随机访问元素上的性能差异

创建 ArrayList 和 LinkedList,分别往两个列表中插入 10 万条数据,接着随机访问列表中的元素

java 复制代码
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;

public class ReadTests {

    private static final int SIZE = 100000;

    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < SIZE; i++) {
            arrayList.add(i);
        }

        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < SIZE; i++) {
            linkedList.add(i);
        }

        long start = System.currentTimeMillis();
        readArrayList(arrayList);
        long end = System.currentTimeMillis();
        System.out.println("ArrayList read time: " + (end - start) + "ms");

        start = System.currentTimeMillis();
        readLinkedList(linkedList);
        end = System.currentTimeMillis();
        System.out.println("LinkedList read time: " + (end - start) + "ms");
    }

    private static void readArrayList(ArrayList<Integer> arrayList) {
        Random random = new Random();
        for (int i = 0; i < SIZE; i++) {
            arrayList.get(random.nextInt(arrayList.size()));
        }
    }

    private static void readLinkedList(LinkedList<Integer> linkedList) {
        Random random = new Random();
        for (int i = 0; i < SIZE; i++) {
            linkedList.get(random.nextInt(linkedList.size()));
        }
    }

}

测试结果如下(当然,严谨的做法是做多次测试)

可以看到,ArrayList 随机访问元素的性能是 LinkedList 的几百倍

3. 插入元素和删除元素的性能

  • ArrayList 在数组尾部添加元素或删除元素的性能较好,因为在数组尾部添加元素或删除元素不涉及数组中元素的移动,但是在数组中间或数组头部插入元素或删除元素的时候,ArrayList 会涉及到数组中元素的移动,性能相对较低
  • LinkedList 在任意位置插入元素或删除元素的性能比较好,因为只需要调整链表中指针的指向

4. 内存占用

  • ArrayList 使用数组来存储元素,占用的空间是连续的,可能会产生内存碎片

  • LinkedList 通过链表来存储元素,每个元素都包含前后节点的引用,占用的空间相对较大

5. 使用场景

  • ArrayList 更适合随机访问操作频繁的场景
  • LinkedList 更适合插入和删除操作频繁的场景,尤其是插入和删除操作发生在列表的头部时

6. 频繁地进行插入操作,ArrayList和LinkedList哪个性能更好

按照上面的分析,如果需要频繁地进行插入操作,LinkedList 的性能是更好的,因为进行插入操作时, LinkedList 只需要调整链表中指针的指向,而 ArrayList 进行插入操作时,可能会涉及到数组元素的移动,性能较差

然而,事实真的如此吗,我们来做一个测试

6.1 在列表头部频繁地进行插入操作

java 复制代码
import java.util.ArrayList;
import java.util.LinkedList;

public class HeadInsertTests {

    private static final int SIZE = 100000;

    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();

        LinkedList<Integer> linkedList = new LinkedList<>();

        long start = System.currentTimeMillis();
        for (int i = 0; i < SIZE; i++) {
            arrayList.add(0, i);
        }
        long end = System.currentTimeMillis();
        System.out.println("ArrayList head insert time: " + (end - start) + "ms");

        start = System.currentTimeMillis();
        for (int i = 0; i < SIZE; i++) {
            linkedList.addFirst(i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList head insert time: " + (end - start) + "ms");
    }

}

测试结果如下

不出意外,在列表头部频繁地进行插入操作,LinkedList 的性能比 ArrayList 高

6.2 在列表尾部频繁地进行插入操作

java 复制代码
import java.util.ArrayList;
import java.util.LinkedList;

public class TailInsertTests {

    private static final int SIZE = 100000;

    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();

        LinkedList<Integer> linkedList = new LinkedList<>();

        long start = System.currentTimeMillis();
        for (int i = 0; i < SIZE; i++) {
            arrayList.add(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("ArrayList tail insert time: " + (end - start) + "ms");

        start = System.currentTimeMillis();
        for (int i = 0; i < SIZE; i++) {
            linkedList.addLast(i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList tail insert time: " + (end - start) + "ms");
    }

}

测试结果如下

可以看到,在列表尾部频繁地进行插入操作,虽然 LinkedList 的性能还是比 ArrayList 高,但是差距已经非常小


我们把数据量提到 100 万,再次进行测试

可以看到数据量提到 100 万时,在列表尾部频繁地进行插入操作,ArrayList 的性能已经远远高于 LinkedList


但是,我们忽略了一个因素,ArrayList 的括容操作也会造成一定的性能消耗,那如果我们在创建 ArrayList 时就指定数组的大小呢


测试结果如下

可以发现 ArrayList 的性能又提升了一点点

如果我们事前能够预料到数据量的范围,可以为 ArrayList 指定一个大小,避免因为扩容而造成不必要的性能开销

6.3 在列表的随机位置频繁地进行插入操作

java 复制代码
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;

public class RandomInsertTests {

    private static final int SIZE = 100000;

    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();

        LinkedList<Integer> linkedList = new LinkedList<>();

        Random random = new Random();

        long start = System.currentTimeMillis();
        for (int i = 0; i < SIZE; i++) {
            int randomNumber = random.nextInt(SIZE);
            int randomIndex = random.nextInt(arrayList.size() + 1);
            arrayList.add(randomIndex, randomNumber);
        }
        long end = System.currentTimeMillis();
        System.out.println("ArrayList random insert time: " + (end - start) + "ms");

        start = System.currentTimeMillis();
        for (int i = 0; i < SIZE; i++) {
            int randomNumber = random.nextInt(SIZE);
            int randomIndex = random.nextInt(linkedList.size() + 1);
            linkedList.add(randomIndex, randomNumber);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList random insert time: " + (end - start) + "ms");
    }

}

测试结果如下

为什么在列表的随机位置频繁地进行插入操作,ArrayList 的性能比 LinkedList 还要高呢,插入操作不是 LinkedList 的优势吗

光从算法层面来讲

  • 数组随机访问的时间复杂度是 O~(1)~,插入操作的时间复杂度是 O~(n)~
  • 链表随机访问的时间复杂度是 O~(n)~,插入操作的时间复杂度是 O~(1)~

但 LinkedList 插入的时间复杂度并不是 O~(1)~,我们看一下 LinkedList 的 add 方法的源码

LinkedList 在插入元素前,会花费 O~(n)~ 的时间复杂度去找到要插入的位置

7. 扩展:影响ArrayList和LinkedList性能的因素

事实上,ArrayList 和 LinkedList 在性能上的差别还与内存和缓存有很大的关联

ArrayList 在内存中是紧凑排列的,可以利用空间局部性,这意味着 ArrayList 比 LinkedList 更适合缓存,而 LinkedList 是分布在整个内存上的,不适合缓存


空间局部性的概念(人工智能给出的回答,仅供参考):

在计算机科学中,空间局部性(Spatial Locality)是指当程序访问了某个存储位置或指令时,那么在不久的将来很可能访问与其存储位置相邻的数据或指令。这一概念基于程序执行的统计特性,也是计算机体系结构中缓存设计的重要基础

具体来说,空间局部性体现在以下方面:

  1. 编程中的体现 :例如,在循环中访问数组时,由于数组在内存中是连续存储的,程序依次访问数组元素,这就体现了空间局部性。访问 array[i]array[i+1] 时,程序会访问相邻的内存地址
  2. 实际编程开发的影响:理解空间局部性对于编写高性能的程序非常关键。现代处理器利用多级缓存来提高性能,而空间局部性可以显著提高缓存命中率,减少内存访问延迟
相关推荐
张国荣家的弟弟6 分钟前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S17 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos25 分钟前
C++----------函数的调用机制
java·c++·算法
唐叔在学习29 分钟前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
是小崔啊42 分钟前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
ALISHENGYA1 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
黄公子学安全1 小时前
Java的基础概念(一)
java·开发语言·python
liwulin05061 小时前
【JAVA】Tesseract-OCR截图屏幕指定区域识别0.4.2
java·开发语言·ocr
jackiendsc1 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
Yuan_o_1 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端