文章目录
- 前言:笔者也是一个初学者,如果所写内容有错的地方,还请见谅,并帮忙提醒更正,谢谢。
介绍一下ArrayList与LinkedList
- 有这样一道经典的面试题:
ArrayList与LinkedList的区别是什么?
- 我在这附上答案
数据结构不同:
- ArrayList的底层是数组,且是动态数组,在内存中的空间是连续的;为什么是动态数组?因为ArrayList有一套自己的动态扩容机制,这里不在详细介绍,有兴趣可以自行了解。
- 而LinkedList的底层是链表,且为双向链表,在内存中的空间是不连续的,因为是通过指针联系在一起。有着头结点和尾结点,前一个元素的尾结点last指向后一个元素的头结点prev,最后一个元素的尾结点last指向第一个元素的头结点prev。
线程安全问题:
- 其实这两位都是线程不安全的,但是因为我们平时是在方法内部作为局部变量使用,所以线程安全这一块可以忽略不计。
查询效率不同:
- ArrayList是通过下标来查找元素的,所以能够快速定位到元素位置,查询效率快。
- LinkedList通过指针指向下一个元素或上一个元素,相对而言,需要一个个找,查询效率慢。
增删效率不同:
- ArrayList因为在内存中是连续的,在增加删除元素的时候需要重排数组结构,这个过程所消耗的资源是比较多的,所以增删效率慢。
- 而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是什么呢?