毁三观的“ArrayList”与“LinkedList”,ArrayList与LinkedList的效率问题!

  • 前言:笔者也是一个初学者,如果所写内容有错的地方,还请见谅,并帮忙提醒更正,谢谢。

介绍一下ArrayList与LinkedList

  • 有这样一道经典的面试题:

ArrayList与LinkedList的区别是什么?

  • 我在这附上答案

数据结构不同:

  1. ArrayList的底层是数组,且是动态数组,在内存中的空间是连续的;为什么是动态数组?因为ArrayList有一套自己的动态扩容机制,这里不在详细介绍,有兴趣可以自行了解。
  2. 而LinkedList的底层是链表,且为双向链表,在内存中的空间是不连续的,因为是通过指针联系在一起。有着头结点和尾结点,前一个元素的尾结点last指向后一个元素的头结点prev,最后一个元素的尾结点last指向第一个元素的头结点prev。

线程安全问题:

  1. 其实这两位都是线程不安全的,但是因为我们平时是在方法内部作为局部变量使用,所以线程安全这一块可以忽略不计。

查询效率不同:

  1. ArrayList是通过下标来查找元素的,所以能够快速定位到元素位置,查询效率快
  2. LinkedList通过指针指向下一个元素或上一个元素,相对而言,需要一个个找,查询效率慢

增删效率不同:

  1. ArrayList因为在内存中是连续的,在增加删除元素的时候需要重排数组结构,这个过程所消耗的资源是比较多的,所以增删效率慢
  2. 而LinkedList只需要改变指针指向就可以实现添加或删除操作,所以相对而言增删效率快

可能还有若干…欢迎总结

哪里毁三观呢?

  • 好,笔者在上面已经附上了相对而言比较标准的答案,但是疑问来了,我说毁三观,到底哪里毁三观呢?
  • 我刚所说的,ArrayList查询效率快,增删效率慢;LinkedList查询效率慢,增删效率快。而现在我要反问一句,真的是这样吗?

原因

  • 好了笔者也不卖关子,ArrayList查询效率确实快,这是不争的事实,但是增删效率不一定不LinkedList“差”,原因就是基于一定数据量的前提下,可以打破我之前所说的。

亲身试验对比一下

  • OK,接下来代码说话。首先我们先写一个测试类:
public class ListTest {

    private static List<String> array = new ArrayList<>();
    private static List<String> linked = new LinkedList<>();

}

查询效率

1.百万级数据量,编写测试方法。

/**
  * 百万级别查询比较 0 0
  */
 @Test
 public void millionQeuryTest(){

     // 各自添加两百万数据
     for (int i = 0; i < 2000000; i++) {
         array.add("array");
         linked.add("link");
     }

     long startTime = System.currentTimeMillis();
     System.out.println(array.get(1999999));
     long endTime = System.currentTimeMillis();
     System.out.println("ArrayList查询耗时:" + (endTime-startTime) + "ms");

     long startTime1 = System.currentTimeMillis();
     System.out.println(linked.get(1999999));
     long endTime1 = System.currentTimeMillis();
     System.out.println("LinkedList查询耗时:" + (endTime1-startTime1) + "ms");

 }

结果如下:
在这里插入图片描述
可以看到,结果没有什么差别。

2.五百万数据量,编写测试方法

/**
  * 五百万级别查询比较
  */
 @Test
 public void fiveMillionQeuryTest(){
     // 各自添加五百万数据
     for (int i = 0; i < 5000000; i++) {
         array.add("array");
         linked.add("link");
     }

     long startTime = System.currentTimeMillis();
     System.out.println(array.get(4999999));
     long endTime = System.currentTimeMillis();
     System.out.println("ArrayList查询耗时:" + (endTime-startTime) + "ms");

     long startTime1 = System.currentTimeMillis();
     System.out.println(linked.get(4999999));
     long endTime1 = System.currentTimeMillis();
     System.out.println("LinkedList查询耗时:" + (endTime1-startTime1) + "ms");

 }

结果如下:
在这里插入图片描述
可以看到,也并无差别。

3.千万级数据量,编写测试方法

/**
  * 千万级数据查询
  */
 @Test
 public void tenMillionQeuryTest(){

     // 各自添加一千万数据
     for (int i = 0; i < 10000000; i++) {
         array.add("array");
         linked.add("link");
     }

     long startTime = System.currentTimeMillis();
     System.out.println(array.get(4999999));
     long endTime = System.currentTimeMillis();
     System.out.println("ArrayList查询耗时:" + (endTime-startTime) + "ms");

     long startTime1 = System.currentTimeMillis();
     System.out.println(linked.get(4999999));
     long endTime1 = System.currentTimeMillis();
     System.out.println("LinkedList查询耗时:" + (endTime1-startTime1) + "ms");
 }

结果如下:
在这里插入图片描述
可以看到ListedList在千万级数据量下才和ArrayList有较为明显的差别。
接下来我们看看增删侠效率。

增删效率

  • 对比执行删除和添加,由于之前对比过查询,发现千万级数据可以看出明显比较,所以接下来我们也是使用千万级数据来做对比。
  • 增删对比会分为:头插法、尾插法以及中部删除。
  • 首先编写测试类,这里偷点懒价格静态代码块
public class ListTest2 {

    /**
     *  对比执行删除和添加:
     *  由于之前对比过查询,发现千万级数据可以看出明显比较,所以接下来我们也是使用千万级数据来做对比
     */
    private static List<String> array = new ArrayList<>();
    private static List<String> linked = new LinkedList<>();

    static {
        // 各自添加一千万数据
        for (int i = 0; i < 10000000; i++) {
            array.add("array");
            linked.add("link");
        }
    }
}

增加元素效率

1. 头部增加

代码如下:

 /**
  * 头部添加对比
  */
 @Test
 public void headAdd(){

     long startTime = System.currentTimeMillis();
     linked.add(0,"link");
     long endTime = System.currentTimeMillis();
     System.out.println("LinkedList头部添加耗时:" + (endTime-startTime) + "ms");

     long startTime1 = System.currentTimeMillis();
     array.add(0,"array");
     long endTime1 = System.currentTimeMillis();
     System.out.println("ArrayList头部添加耗时:" + (endTime1-startTime1) + "ms");
 }

结果如下:
在这里插入图片描述
头部添加,LinkedList比ArrayList快。因为ArrayList在有近一千万个元素要向后移动,LinkedList只要将指针修改指向即可。

2. 中部增加

代码如下:

 /**
  * 中部添加对比
  */
 @Test
 public void bodyAdd(){

     long startTime = System.currentTimeMillis();
     linked.add(5000000,"link");
     long endTime = System.currentTimeMillis();
     System.out.println("LinkedList中部添加耗时:" + (endTime-startTime) + "ms");

     long startTime1 = System.currentTimeMillis();
     array.add(5000000,"array");
     long endTime1 = System.currentTimeMillis();
     System.out.println("ArrayList中部添加耗时:" + (endTime1-startTime1) + "ms");
 }

结果如下:
在这里插入图片描述中部添加,反而LinkedList慢了下来,这是因为在添加之前,有将近五百万的数据要遍历查询,随后在执行增加操作,而我们知道本身LinkedList查询就是较ArrayList慢的,所以结果是这样

3. 尾部增加

代码如下:

/**
 * 尾部添加对比
 */
@Test
public void endAdd(){

    long startTime = System.currentTimeMillis();
    linked.add("link");
    long endTime = System.currentTimeMillis();
    System.out.println("LinkedList尾部添加耗时:" + (endTime-startTime) + "ms");

    long startTime1 = System.currentTimeMillis();
    array.add("array");
    long endTime1 = System.currentTimeMillis();
    System.out.println("ArrayList尾部添加耗时:" + (endTime1-startTime1) + "ms");
}

结果如下:
在这里插入图片描述

尾部添加,ArrayList不需要重排结构,只需在末尾添加,所以时间和LinkedList相近。

删除元素效率

  • 由于删除和增加类似,我就只是演示一下即可。
1. 头部删除

代码如下:

 /**
  * 头部添加对比
  */
/**
  * 头部删除对比
  */
 @Test
 public void headRemove(){

     long startTime = System.currentTimeMillis();
     linked.remove(0);
     long endTime = System.currentTimeMillis();
     System.out.println("LinkedList头部删除耗时:" + (endTime-startTime) + "ms");

     long startTime1 = System.currentTimeMillis();
     array.remove(0);
     long endTime1 = System.currentTimeMillis();
     System.out.println("ArrayList头部删除耗时:" + (endTime1-startTime1) + "ms");
 }

结果如下:
在这里插入图片描述

头部删除,LinkedList比ArrayList快。

2. 中部删除

代码如下:

 /**
   * 中部删除对比
   */
  @Test
  public void bodyRemove(){

      long startTime = System.currentTimeMillis();
      linked.remove(5000000);
      long endTime = System.currentTimeMillis();
      System.out.println("LinkedList中部删除耗时:" + (endTime-startTime) + "ms");

      long startTime1 = System.currentTimeMillis();
      array.remove(5000000);
      long endTime1 = System.currentTimeMillis();
      System.out.println("ArrayList中部删除耗时:" + (endTime1-startTime1) + "ms");
  }

结果如下:
在这里插入图片描述

中部删除,LinkedList慢。

3. 尾部删除

代码如下:

/**
  * 尾部删除对比
  */
 @Test
 public void endRemove(){

     long startTime = System.currentTimeMillis();
     linked.remove(9999999);
     long endTime = System.currentTimeMillis();
     System.out.println("LinkedList尾部删除耗时:" + (endTime-startTime) + "ms");

     long startTime1 = System.currentTimeMillis();
     array.remove(9999999);
     long endTime1 = System.currentTimeMillis();
     System.out.println("ArrayList尾部删除耗时:" + (endTime1-startTime1) + "ms");
 }

结果如下:
在这里插入图片描述

尾部删除,ArrayList不需要重排结构,只需在末尾删除元素,所以时间和LinkedList相近。

  • OK这就是本博文所展示的一个小知识点,ArrayList增删效率不一定比LinkedList慢,因为在一定数据量的加持下,LinkedList还需要考虑查询元素的时间再去操作元素。

思考题?

  • 我们知道,HashMap是线程不安全的,而CurrentHashMap是线程安全的。那为什么我们平时使用的时候还是HashMap的情况占大多数?
  • 提示:这里笔者只讲我自己知道的哈。我也要去详细了解一哈!我们平时写代码是基于什么?(Spring框架!即答),而Spring容器中存放bean的单例池SingletonObjects是什么呢?
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值