List是封装了针对线性表操作的接口,ArrayList和LinkedList是在项目里用的比较多的两个实现类。我在面试时一般会问,它们两者有什么差别?很多人能回答说,ArrayList是基于数组实现,而LinkedList是基于双向链表实现。
学过数据结构的同学都知道,如果我们要查找数组里的某个元素,可以根据如下的公式很快地定位到该元素的位置。
第i号元素的位置 = 第0号元素的位置+(i-1)*每个元素的长度。
但数组不擅长添加和删除元素,比如要在长度是10000的数组里的500号位置添加一个元素,我们首先得让500到10000号元素都往后移动一位,先把500号位置腾出来,随后再添加。删除也是类似。
和数组相对应,链表比较擅长添加和删除元素(只需要更改其中一个元素的指针即可),但不擅长于定位(如果要找500号元素,就得从0号开始一个个找)。
这种说法应付面试尚可,但在实际项目里,往往没有单纯的添加(或删除)和单纯的查找操作,一般是两者配合使用。比如我们会在一个for循环里通过add方法从头开始在尾部添加元素,完成后在另个地方通过get方法从头开始获取元素,这两个动作往往会配对出现
那么在这种情况下,该选用哪种集合?下面我们来通过ListCompare.java来比较一下这两类List的各种性能,从而来归纳它们的使用场景。
1 //省略必要的import集合包的代码
2 public class ListCompare {
3 //在尾部添加元素
4 static void testAddatTail(List list,String type) {
5 int size = 1000000;
6 long start=System.currentTimeMillis();
7 //通过for循环在尾部添加元数据
8 for(int i = 0;i<size;i++)
9 {list.add(i);}
10 long end=System.currentTimeMillis();
11 System.out.println("testAddatTail for " + type);
12 //结束时间减开始时间就是运行时间
13 System.out.println(end - start);
14 }
15 //随机查找元素
16 static void testRandomSearch(List list,String type) {
17 Random rand = new Random();
18 long start=System.currentTimeMillis();
19 //在for循环里随机查找元素
20 for(int i = 0;i<10000;i++)
21 {list.indexOf(rand.nextInt(100000));}
22 long end=System.currentTimeMillis();
23 System.out.println("testRandomSearch for " + type);
24 System.out.println(end - start);
25 }
26 //随机地添加元素
27 static void testAddatRandom(List list,String type){
28 Random rand = new Random();
29 long start=System.currentTimeMillis();
30 for(int i = 0;i<1000;i++)
31 {list.add(rand.nextInt(100000), "0");}
32 long end=System.currentTimeMillis();
33 System.out.println("testAddatRandom for " + type);
34 System.out.println(end - start);
35 }
36 //主方法
37 public static void main(String[] args) {
38 //创建两种不同类型的List
39 List arrayList = new ArrayList();
40 List linkedList = new LinkedList();
41 //如下是对比实现
42 testAddatTail(arrayList,"ArrayList");
43 testAddatTail(linkedList,"LinketList");
44 testAddatRandom(arrayList,"ArrayList");
45 testAddatRandom(linkedList,"LinketList");
46 testRandomSearch(arrayList,"ArrayList");
47 testRandomSearch(linkedList,"LinketList");
48 }
49 }
在下表3.1里,我们列出了在上述代码里定义的方法。
表3.1 针对两种不同集合对比测试方法归纳表
方法名 | 行数 | 动作 |
testAddatTail | 第4到14行 | 通过for循环在尾部添加1000000个元素 |
testRandomSearch | 第16到25行 | 从集合的随机位置拿元素,相当于随机查找 |
testAddatRandom | 第27到35行 | 在集合的随机位置添加元素 |
在main函数的39和40行,我们定义了两种List,随后从42到47行,我们分别针对两种不同的List集合执行了对比测试方法,如果我们运行多次,输出的时间未必都相等,但从中我们能看到两者的对比,根据其中的一次运行结果,我们能整理出如下表3.2所示的结论。
表3.2 运行结果分析表
比较项 | 集合种类 | 运行时间 | 分析 |
在尾部添加 | ArrayList | 141 | ArrayList是基于数组,和基于链表的ListedList相比,能很快地定位到尾部 |
LinkedList | 219 | ||
在随机位置添加 | ArrayList | 938 | 这里包含查找位置和添加两个动作,查找时基于数组的ArrayList占优,添加时基于LinkedList占优,综合下来还是LinkedList占优 |
LinkedList | 672 | ||
在随机位置查找 | ArrayList | 4109 | 基于数组的ArrayList很占优,相比基于链表的Linkedlist就需要消耗一定的代价了。 |
LinkedList | 5828 |
也就是说,如果被问到这类问题,你可以说出如下的结论。
第一,如果我们就一次性地通过add从集合尾部添加元素,添加完成后我们也只需读取一次,那么建议使用ArrayList,因为从上表来看,它两个动作的运行时间总和要小于LinketList。
第二,如果要频繁地添加元素,或者在完成添加元素后会频繁地通过indexOf方法从集合里查找元素,可以使用LinketList,理由是它的随机添加和随机查找的总时间消耗要小于ArrayList。
第三,请记得,如果在代码里indexOf的操作过于频繁从而成为项目运行的瓶颈时,可以考虑后文里提到的HashMap对象。
此外,ArrayList和Linkedlist都是线程不安全的,别把这点说成是它们的差别。
关于集合类的面试文章汇总:
Java集合方面的面试题:对比ArrayList和Vector对象,分析Vector为什么不常用
Java集合方面的面试题:ArrayList和LinkedList有什么差别?分别适用于哪些场景?
Java集合方面的面试题:TreeSet、HashSet和LinkedHashSet的各自特点
这是我的公众号,其中包含了大量面试文章,同时我自己出了多本Python和Java方面的书籍,会定期在公众号里发书的电子版。请大家关注下我的公众号,谢谢了。