ArrayList和LinkedList在查询和插入时的性能对比以及缓存局部性试验


前言

一般我们都知道ArrayList的底层数据结构是动态数组,特点是查询快、增删慢;LinkedList的底层数据结构是双向链表,特点是增删快、查询慢。本文将从另一个角度分析ArrayList和LinkedList在查询插入时的性能对比,并进行缓存局部性试验


1.查询

起初打算使用50000000个数据进行实验,但是LinkedList一直运行不出来,索性使用了100000个数据简单演示了一下,结果如下:
所用代码:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Demo04 {
    public static void main(String[] args) {
        arrayQuery();
        linkedQuery();
    }

    //ArrayList查找
    public static void arrayQuery() {
        List<Integer> list = new ArrayList<>();
        //准备数据
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        //记录开始时间
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            list.get(i);
        }
        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("arrayQuery = " + (end - start) + " ms");
    }

    //LinkedList查找
    public static void linkedQuery() {
        //准备数据
        List<Integer> list = new LinkedList<>();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        //记录开始时间
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            list.get(i);
        }
        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("linkedQuery = " + (end - start) + " ms");
    }
}

输出结果:ArrayList比LinkedList所用时间少
查询
分析:
1.底层数据结构方面:ArrayList可以通过下标直接快速访问元素,时间复杂度为O(1);LinkedList需要从头节点或尾节点开始进行遍历,逐个节点地移动,直到找到目标位置的节点,时间复杂度为O(n),其中n是链表的长度;
2.硬件方面:缓存局部性的空间局部性,即如果一个数据被访问,那么与它相邻的数据在不久的将来很可能被访问,相邻的元素也会被加载到内存中。数组在内存中是连续存储的,而链表是不连续的,那么ArrayList元素的缓存命中率就会比LinkedList元素的高,进而花费的时间短。

2.插入

2.1插入-尾部

使用1000000个数据每次都插入在尾部,结果如下:
所用代码:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Demo05 {
    public static void main(String[] args) {
        arrayAdd();
        linkedAdd();
    }
    //ArrayList插入尾部
    public static void arrayAdd() {
        List<Integer> list = new ArrayList<>();
        //记录开始时间
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            list.add(i);
        }
        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("arrayAdd= " + (end - start) + " ms");
    }

    //LinkedList插入尾部
    public static void linkedAdd() {
        List<Integer> list = new LinkedList<>();
        //记录开始时间
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            list.add(i);
        }
        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("linkedAdd = " + (end - start) + " ms");
    }
}

输出结果:ArrayList比LinkedList所用时间少
插入-尾部
分析:
虽然在底层数据结构方面,LinkedList插入更有优势,但是在硬件方面看,ArrayList更好。

2.2插入-不在尾部

先准备500000个数据,然后使用500000个数据不插入尾部,结果如下:
所用代码:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Demo06 {
    public static void main(String[] args) {
        arrayAdd();
        linkedAdd();
    }

    //ArrayList不插入尾部
    public static void arrayAdd() {
        List<Integer> list = new ArrayList<>();
        //准备数据
        for (int i = 0; i < 500000; i++) {
            list.add(i);
        }
        //记录开始时间
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            list.add(i, i);
        }
        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("arrayAdd = " + (end - start) + " ms");
    }

    //LinkedList不插入尾部
    public static void linkedAdd() {
        List<Integer> list = new LinkedList<>();
        //准备数据
        for (int i = 0; i < 500000; i++) {
            list.add(i);
        }
        //记录开始时间
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            list.add(i, i);
        }
        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("linkedAdd = " + (end - start) + " ms");
    }
}

输出结果:ArrayList比LinkedList所用时间少
插入-不在尾部
分析:
虽然在底层数据结构方面,LinkedList插入更有优势,但是在硬件方面看,ArrayList更好。

3.缓存局部性试验

创建一个15000行15000列的二维数组,然后使用两种方式对其遍历进行赋值操作,分别是:
先行后列:a[0][0], a[0][1], a[0][2]…②先列后行:a[0][0], a[1][0], a[2][0]…
运行时间结果如下:
所用代码:

public class Demo07 {
    public static void main(String[] args) {
        test1();
        test2();
    }

    // a[0][0], a[0][1], a[0][2]...
    public static void test1() {
        int[][] arr = new int[15000][15000];
        //记录开始时间
        long start = System.currentTimeMillis();
        for (int i = 0; i < 15000; i++) {
            for (int j = 0; j < 15000; j++) {
                arr[i][j] = 1;
            }
        }
        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("先行后列耗时:" + (end - start) + " ms");
    }

    // a[0][0], a[1][0], a[2][0]...
    public static void test2() {
        int[][] arr = new int[15000][15000];
        //记录开始时间
        long start = System.currentTimeMillis();
        for (int i = 0; i < 15000; i++) {
            for (int j = 0; j < 15000; j++) {
                arr[j][i] = 1;
            }
        }
        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("先列后行耗时:" + (end - start) + " ms");
    }
}

输出结果:先行后列赋值比先列后行赋值所用时间
缓存局部性试验
分析:
在Java中,二维数组在内存中通常是按照行优先的方式进行线性存储的。先存储第一行的所有元素,然后再存储第二行的元素,以此类推,直到存储完最后一行的元素。也就是说,内存中是连续地存储二维数组的各个元素,一行接一行。
这里也用到了缓存局部性的空间局部性,即如果一个数据被访问,那么与它相邻的数据在不久的将来很可能被访问,相邻的元素也会被加载到内存中。所以先行后列赋值顺序的缓存命中率是高于先列后行的,因为前者的元素在内存中是连续的,而后者是不连续的。二维数组在内存中的布局如下:
二维数组在内存中的布局


总结

大部分情况下,ArrayList要比LinkedList要快,所以优先考虑使用ArrayList,如果使用LinkedList需要进行讨论。另外通过缓存局部性试验发现合理的利用缓存局部性原理可以显著提高程序的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值