《张三求职日记》基础篇--(1)ArrayList和LinkedList

449 阅读11分钟
写在前面:有关于List的问题其实对于1-3年的程序员来说并不是高频问题,因为这是这一行入门必须要掌握的知识,而且日常中绝大部分程序员肯定经常用到,再加上可以问的点不多,有一些面试官往往不屑于问List相关的问题。不过如果被问到了,如果答不出来或者答的不好,还是很减分的。这一篇能够很好的帮助大家排坑。有关于张三的背景介绍参考:juejin.cn/post/684490…

张三开开心心的开始了第一次面试。

面试官:先来个自我介绍吧。
张三:balabalabala。
面试官:我看你的简历里写到了集合,那你来聊聊ArrayList和LinkedList吧,想到什么说什么。

张三心想,怎么感觉面试不按套路出牌啊,我准的面试题都是一问一答模式的,这个面试官怎么一来不问我,就只让我自己说啊,那我只能把之前印象里的全说一遍了,幸好我平时也是看博客,面试前刷过题的,问这个应该对我问题不大吧。于是张三和面试官确认了一遍是随意说吗,就整理了一下思路,继续回答

张三:ArrayList的底层是动态数组,LinkedList的底层是双向链表。相对来说ArrayList的查询快,而添加和删除的速度Linkedlist快。……(面试官并没有打断所以思考了一会)……ArrayList的扩容应该是通过新建一个新数组然后把原数组复制进去而办到的,会扩容成原来的1.5倍,……ArrayList的默认容量为10。

张三心想,一般博客上也就写了这些东西啊,我不仅都回答上来了,甚至你不问我我都把扩容和默认容量都回答出来了,应该能够折服面试官吧

面试官:你刚刚说的是ArrayList查询快,LinkedList插入删除快,确定吗?

张三心想,难道不是吗,博客上和面试题里都是这样说的啊,但是看着面试官这样问了,还像是在打字记录什么,心开始虚了

张三:确定吧……

面试官:那ArrayList添加元素的时间复杂度是多少?

张三心想,完了,没准备,只能硬着头皮上了

张三:我记得ArrayList插入元素需要移动数组,复杂度应该是O(n)吧。

面试官:那LinkedList呢?

张三:LinkedList是更改指针的引用,应该是O(1)吧。

面试官:O(1)是吗? //面试官又在打字记录中

张三:插入前应该还要遍历一下,所以应该是O(n)。

面试官:那Linkedlist的插入为什么比ArrayList快呢?

张三彻底凌乱了,一时不知道如何回答

张三:……我看博客上是这样说的啊,应该是移动数组比较慢吧。

于是后面的面试过程张三一直抱着忐忑的心完成的,也表现的不太自信了,之后的省略。
面试官:这次的面试就先到这里,后面会有我们的HR联系你。

张三:好的……

------------------------------------------------------------------------------------------------

张三出现的问题我相信对绝大部分人都有警醒作用:
  1. 张三仅仅只是背了面试题就觉得自己的技术很不错了。
  2. 张三获取知识的同时并没有对知识有过多的思考。
  3. 张三没有实际操作过代码过所以底气不足。

为什么程序员入职需要面试呢,为了设置题目考倒大家,通过分数高低决定谁能够入职吗?并不完全是,我认为面试还是检验大家的学习能力,思考问题的能力,以及解决问题的能力。而仅仅通过List相关的面试题,就能看出张三在这三点上都有欠缺,所以通过这个问题企业就能排除一部分人。也许张三的回答对于某些企业来说已经是不错的回答了,但是对于对技术有追求的人来说,还是不够的。面试官大概率也是经历过残酷的面试上来的,所以接下来由我来对上面的问题刨根到底,希望能对大家有所帮助,希望大家能够带着自己的思考来回答问题。

首先,ArrayList、LinkedList插入的时间复杂度是多少究竟该如何回答?

在讲概念之前,我们先上代码,也建议大家自己动手敲一下,代码很简单,但是自己运行出来的效果可以让自己受益匪浅。

ArrayList直接添加耗时:

LinkedList直接添加耗时:


看到代码的运行结果是不是出乎很多人的意外?不是说LinkedList插入快吗?为什么Linked的耗时反而比ArrayList长呢?而且还长不少?

不要急咱们来看下面两段代码的执行时间,不过由于用原来的数量的添加执行速度实在太慢,在这里我们减小了20倍的数量级。

ArrayList选坐标耗时:

LinkedList选坐标耗时:

看到这,聪明的小伙伴或者之前去了解过的小伙伴或者在大学里是学霸的小伙伴已经能够清楚说明原因了。

而之前欠过没有技术的债的小伙伴已经一头雾水了--这这这是什么情况。

而更聪明的小伙伴已经把我注释里的代码读了一遍并自己敲了一遍验证了,更加明白了具体的原因。

所以我就用通俗易懂的话给大家解释这里面的奥秘了,毕竟如果我们要回答面试官的问题,也不太可能专业术语配着图表来回答吧,如果后面我讲的你能够听懂,那你肯定面试时也能讲的明白。

前面说了,ArrayList的底层是动态数组(究竟真的是不是建议大家点一下源码看看),前面说的ArrayList添加元素需要移动数组(其实真正的原理应该是复制)也是对的,但是直接调用add()方法时,ArrayList是直接在数组的最后面添加,也就不用复制了。而LinkedList直接调用add()方法时也是在最后添加,但是LinkedList的底层是双向链表,而双向链表要加一个结点需要做些什么相信大部分人还是清楚的,所以测试结果反而是ArrayList快。但是,毕竟问的是时间复杂度嘛,在这调用这个方法的情况下,ArrayList和LinkedList的插入时间复杂度都是O(1)。

虽然都是O(1)的时间复杂度,但也请记住结论,千万别再死背LinkedList的插入比ArrayList快了。


                                      


当然还没完,根据两个选坐标的图片的结果LinkedList可是比ArrayList快了好多好多呢,这又是为什么呢?

因为指定了插入的位置啊,源码这里就不带大家看了,有兴趣的小伙伴可以自己点开看看。用通俗的话解释就是,ArrayList的插入如果指定了一个位置,那么对不起,我就是要插入在这里。假设数组是一群人在食堂排队打饭,你说你想插个队,你就是要站在原来排第三的人的这个位置上,排第三的这位和后面的都敢怒不敢言,让你插队了,但是会带来了一个问题,你站原来排第三的这位这里,原来排第三的这位站哪?于是排第三的这位后退了一步,排第四的也跟着后退了一步,一直到最后一位也后退了一步。天啊,因为你想插队,从你之后的全部的人都后退了一步,这个消耗可是真的大。当然对于数组来说,后退的这一步是同时的,原理是数组的复制,愿意深挖的同学可以自己去深入了。所以综合而言,ArrayList的add(index, E) 方法,指定插入的位置的时间复杂度是O(n)。

那我们看到,LinkedList指定位置插入的速度非常快,n多倍优于ArrayList了,这又是什么原因呢?

张三之前也讲到了,LinkedList的底层是双向链表,双向链表用通俗的话来讲不是很好讲清楚,希望我的这个比喻能够让敲过链表的同学能够理解。就是一群小朋友在操场做游戏,他们每个人都有自己的学号,如果我要找一个人,我只能先找当前第一个小朋友也就是1号小朋友,而小朋友之间只能找学号相邻的小朋友,但是小朋友已经在操场上跑散了,如果我想找20号小朋友的话,小朋友直间要碰面20次才能找到那个小朋友……天啊,是不是感觉很麻烦。而上面的那个情况是,我每次想加一个小朋友和他们一起玩游戏,但是我加在了第一个位置,我只需要找到一号小朋友并给他新同学的学号,并告诉他以后你可以找他,他也可以找你就行了,而我只能找这个新的小朋友了。由于每个小朋友都放到了第一个位置,而我其实每次找一次就够了。其实这种情况下虽然指定了位置,但本质上并没有遍历链表,仅仅只是修改了第一个位置的指针引用而已,速度还是相当快的,如果有人把数值改为之前那个量级,应该能够发现时间应该是差不多的,时间复杂度都为O(1),所以快。

那么问题来了LinkedList的插入真的比ArrayList快吗?

前面提到了找小朋友的例子,如果又50个小朋友,班级来了第51个小朋友,我就是想让他做游戏时之和20号小朋友和21号小朋友玩,我需要做些什么呢?我需要先找到1号小朋友,然后1号找2号……一直找到20号,然后告诉20号你别和21号小朋友玩了,你和51号玩,再告诉51号小朋友和20号和21号玩,再告诉21号别和20号玩了。天啊……

                            


这流程这么复杂,我想指定位置插个队真的会比排队后撤一步快吗?于是代码说话,我们通过之前的分析可以知道,排队后撤一步最坏的情况时每次都插在最前面,让每个被别人插队的人都感受社会的险恶,所以最坏的情况就是上面的代码的时间。而找小朋友呢?通过分析当然应该是每次都要插到最难找的到的小朋友那里去啊,那里是哪呢?通过我的试验,LinkedList果然是双向链表,选最后一个位置-1速度还挺快,然后由于上面注释的的控制变量的代码模拟当于找小朋友越难找的次数越少,并不是真正的最坏情况,尽管如此,看下图依然是比ArrayList的最坏时间要久不少。


而真正的最坏情况呢?


真的可怕!

那我们可以总结一下怎么回答有关于插入时间复杂度的问题了:
  1. 在直接调用add(E)方法(插入末尾)时,ArrayList和LinkedList的时间复杂度都是O(1),但是ArrayList会比LinkedList还要快(这点估计很多人都没有尝试过)。
  2. 在调用add(index,E)方法(指定位置插入)时,ArrayList和LinkedList的时间复杂度都是o(n),ArrayList插入的位置越靠前效率越低,而LinkedList插入的位置越靠中间效率越低,LinkedList仍没有一定比ArrayList快,反倒是最坏情况要比ArrayList差不少。
  3. 之前我也一直深以为查询多的场景用ArrayList,插入删除多的场景用LinkedList,但是测试结果并不是这样呢,还请大家具体情况具体分析,插入插在哪里对性能影响很大。

那么插入说完了,删除呢?
通过分析和我的实际的验证,删除的情况和添加类似,依然不能够说明LinkedList比ArrayList的速度要快,请大家自行验证,这里就不贴代码了。

我们发现了什么?
  1. 网上的博客质量参差不齐,甚至很多结论都是错的(包括我写的也不一定正确)。
  2. 看博客真的不如自己动手敲代码得出的结论正确,自己要抱着质疑的心来学习知识。
  3. 哪怕时一个很小的知识点,都能够引出一大堆颠覆自己认知的知识,大家要对知识报有敬畏心。
  4. 我们需要把发现的问题思考到自己的项目中,自己有哪些地方用的不对呢?

张三在这次的面试中收获颇丰,我们下周见。