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. 实际编程开发的影响:理解空间局部性对于编写高性能的程序非常关键。现代处理器利用多级缓存来提高性能,而空间局部性可以显著提高缓存命中率,减少内存访问延迟
相关推荐
kingbal5 分钟前
IDEA:Picked up _JAVA_OPTIONS: -Xmx512M
java·ide·intellij-idea
何政@15 分钟前
如何快速自定义一个Spring Boot Starter!!
java·spring boot·spring·自定义配置·springboot自动配置·快速构建一个starter·
Web项目开发33 分钟前
JAVA JDK华为云镜像下载,速度很快
java
夜色呦37 分钟前
利用Spring Boot构建足球青训管理平台
java·spring boot·后端
计算机专业源码37 分钟前
springboot儿童物品共享平台的设计与实现
java·spring boot·后端
尘浮生38 分钟前
Java项目实战II基于Java+Spring Boot+MySQL的购物推荐网站的设计与实现(源码+数据库+文档)
java·开发语言·数据库·spring boot·mysql·maven·intellij-idea
2402_8575893641 分钟前
Spring Boot框架下的足球青训俱乐部后台开发
java·spring boot·后端
2401_8576363942 分钟前
足球青训后台管理系统:Spring Boot实现指南
java·spring boot·后端
杨哥带你写代码44 分钟前
Spring Boot技术在足球青训管理中的实践与挑战
java·spring boot·后端
2401_857636391 小时前
Spring Boot框架下的足球青训俱乐部管理
java·spring boot·后端