《吊打面试官》系列-ArrayList

你知道的越多,你不知道的越多git


点赞再看,养成习惯程序员


本文 GitHub github.com/JavaFamily 已收录,有一线大厂面试点思惟导图,也整理了不少个人文档,欢迎Star和完善,你们面试能够参照考点复习,但愿咱们一块儿有点东西。github

前言

做为一个在互联网公司面一次拿一次Offer的面霸,战胜了无数竞争对手,每次都只能看到无数落寞的身影失望的离开,略感愧疚(请容许我使用一下夸张的修辞手法)。面试

因而在一个寂寞难耐的夜晚,我痛定思痛,决定开始写互联网技术栈面试相关的文章,但愿能帮助各位读者之后面试势如破竹,对面试官进行360°的反击,吊打问你的面试官,让一同面试的同僚瞠目结舌,疯狂收割大厂Offer!算法

全部文章的名字只是个人噱头,咱们应该有一颗谦逊的心,因此但愿你们怀着空杯心态好好学,一块儿进步。数组

正文

一个婀娜多姿,穿着衬衣的小姐姐,拿着一个精致的小笔记本,径直走过来坐在个人面前。缓存

看着眼前这个美丽的女人,心想这不会就是Java基础系列的面试官吧,真香。安全

不过看样子这么年轻应该问不出什么深度的吧,嘻嘻。(哦?是么😏)微信

帅丙,上次面试到如今都过去2个星期你才过来,为啥鸽了这么久?数据结构

美丽迷人的面试官您好,由于以前得了流感,差点就没了,还有最近年末忙年会和年终review的事情,实在太忙了,很差意思。

还作了一波导演(实际上是打杂)去拍摄蘑菇街的年会视频了,实在忙到爆炸,周末也没能怼出文章哈哈。

好吧那我理解了,下次这种状况记得提早跟我说,对了,多喝热水。

面试官最后的多喝热水,直接触动我心里的防线,竟然还有人这么关心我,帅丙的眼角,又湿了……

ArrayList有用过吗?它是一个什么东西?能够用来干吗?

有用过,ArrayList就是数组列表,主要用来装载数据,当咱们装载的是基本类型的数据int,long,boolean,short,byte…的时候咱们只能存储他们对应的包装类,它的主要底层实现是数组Object[] elementData。

与它相似的是LinkedList,和LinkedList相比,它的查找和访问元素的速度较快,但新增,删除的速度较慢。

小结:ArrayList底层是用数组实现的存储。

特色:查询效率高,增删效率低,线程不安全。使用频率很高。

为啥线程 不安全还使用他呢?

由于咱们正常使用的场景中,都是用来查询,不会涉及太频繁的增删,若是涉及频繁的增删,可使用LinkedList,若是你须要线程安全就使用Vector,这就是三者的区别了,实际开发过程当中仍是ArrayList使用最多的。

不存在一个集合工具是查询效率又高,增删效率也高的,还线程安全的,至于为啥你们看代码就知道了,由于数据结构的特性就是优劣共存的,想找个平衡点很难,牺牲了性能,那就安全,牺牲了安全那就快速。

Tip:这里仍是强调下你们不要为了用而用,我记得我之前最开始工做就有这个毛病。就是无论三七二十一,就是为了用而用,别人用我也用,也无论他的性能啊,是否线程安全啥的,所幸最开始公司接触的,都是线下的系统,并且没啥并发。

如今回想起来还有点后怕,并且我第一次出去面试就被一个算法的大佬给虐得体无完肤,其实他问的我都会用,可是就是说不上来为啥用,为啥这么作。

回想起一年前的第一次面试,我又想到了那时候的点滴,那时候我身边还有女人,那时候我还有头发,那时候……个人眼角又湿了。

您说它的底层实现是数组,可是数组的大小是定长的,若是咱们不断的往里面添加数据的话,不会有问题吗?

ArrayList能够经过构造方法在初始化的时候指定底层数组的大小。

经过无参构造方法的方式ArrayList()初始化,则赋值底层数Object[] elementData为一个默认空数组Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}因此数组容量为0,只有真正对数据进行添加add时,才分配默认DEFAULT_CAPACITY = 10的初始容量。

你们能够分别看下他的无参构造器和有参构造器,无参就是默认大小,有参会判断参数。

数组的长度是有限制的,而ArrayList是能够存听任意数量对象,长度不受限制,那么他是怎么实现的呢?

其实实现方式比较简单,他就是经过数组扩容的方式去实现的。

就好比咱们如今有一个长度为10的数组,如今咱们要新增一个元素,发现已经满了,那ArrayList会怎么作呢?

第一步他会从新定义一个长度为10+10/2的数组也就是新增一个长度为15的数组。

而后把原数组的数据,原封不动的复制到新数组中,这个时候再把指向原数的地址换到新数组,ArrayList就这样完成了一次改头换面。

Tip:不少小伙伴说,你举例干吗用10这么长的数组举例,这样画都要多画一点格子了,帅丙我会作无心义的事情?由于咱们在使用ArrayList的时候通常不会设置初始值的大小,那ArrayList默认的大小就恰好是10。

而后大家也能够看到,他的构造方法,若是你传入了初始值大小,那就使用你传入的参数,若是没,那就使用默认的,一切都是有迹可循的。

能具体说下1.7和1.8版本初始化的时候的区别么?

arrayList1.7开始变化有点大,一个是初始化的时候,1.7之前会调用this(10)才是真正的容量为10,1.7即自己之后是默认走了空数组,只有第一次add的时候容量会变成10。

ArrayList的默认数组大小为何是10?

其实我也没找到具体缘由。

听说是由于sun的程序员对一系列普遍使用的程序代码进行了调研,结果就是10这个长度的数组是最经常使用的最有效率的。也有说就是随便起的一个数字,8个12个都没什么区别,只是由于10这个数组比较的圆满而已。

我记得你说到了,他增删很慢,你能说一下ArrayList在增删的时候是怎么作的么?主要说一下他为啥慢。

诶卧*,这个我想一下,大学看的有点忘记了,我想一想。

嗯嗯好的,我分别说一下他的新增的逻辑吧。

他有指定index新增,也有直接新增的,在这以前他会有一步校验长度的判断ensureCapacityInternal,就是说若是长度不够,是须要扩容的。

在扩容的时候,老版本的jdk和8之后的版本是有区别的,8以后的效率更高了,采用了位运算,右移一位,其实就是除以2这个操做。

1.7的时候3/2+1 ,1.8直接就是3/2。

指定位置新增的时候,在校验以后的操做很简单,就是数组的copy,你们能够看下代码。

不知道你们看懂arraycopy的代码没有,我画个图解释下,你可能就明白一点:

好比有下面这样一个数组我须要在index 5的位置去新增一个元素A

那从代码里面咱们能够看到,他复制了一个数组,是从index 5的位置开始的,而后把它放在了index 5+1的位置

给咱们要新增的元素腾出了位置,而后在index的位置放入元素A就完成了新增的操做了

至于为啥说他效率低,我想我不说你也应该知道了,我这只是在一个这么小的List里面操做,要是我去一个几百几千几万大小的List新增一个元素,那就须要后面全部的元素都复制,而后若是再涉及到扩容啥的就更慢了不是嘛。

我问你个真实的场景,这个问题不多人知道,你可要好好回答哟!

ArrayList(int initialCapacity)会不会初始化数组大小?

这是什么问题?卧槽问个ArrayList还能问到知识盲区?

不要慌,我记得丙丙说过:不管咱们遇到什么困难都不要惧怕,打败恐惧最好的办法就是面对他!!!奥利给!!!…

会初始化数组大小!可是List的大小没有变,由于list的大小是返回size的。

并且将构造函数与initialCapacity结合使用,而后使用set()会抛出异常,尽管该数组已建立,可是大小设置不正确。

使用sureCapacity()也不起做用,由于它基于elementData数组而不是大小。

还有其余反作用,这是由于带有sureCapacity()的静态DEFAULT_CAPACITY。

进行此工做的惟一方法是在使用构造函数后,根据须要使用add()屡次。

你们可能有点懵,我直接操做一下代码,你们会发现咱们虽然对ArrayList设置了初始大小,可是咱们打印List大小的时候仍是0,咱们操做下标set值的时候也会报错,数组下标越界。

其实数组是初始化了,可是List没,那size就没变,set下标是和size比较的那就报错了。

再结合源码,你们仔细品读一下,这是Java Bug里面的一个经典问题了,仍是颇有意思的,你们平时可能也不会注意这个点。

ArrayList插入删除必定慢么?

取决于你删除的元素离数组末端有多远,ArrayList拿来做为堆栈来用仍是挺合适的,push和pop操做彻底不涉及数据移动操做。

那他的删除怎么实现的呢?

删除其实跟新增是同样的,不过叫是叫删除,可是在代码里面咱们发现,他仍是在copy一个数组。

为啥是copy数组呢?

继续打个比方,咱们如今要删除下面这个数组中的index5这个位置

那代码他就复制一个index5+1开始到最后的数组,而后把它放到index开始的位置

index5的位置就成功被”删除“了其实就是被覆盖了,给了你被删除的感受。

同理他的效率也低,由于数组若是很大的话,同样须要复制和移动的位置就大了。

ArrayList是线程安全的么?

固然不是,线程安全版本的数组容器是Vector。

Vector的实现很简单,就是把全部的方法通通加上synchronized就完事了。

你也能够不使用Vector,用Collections.synchronizedList把一个普通ArrayList包装成一个线程安全版本的数组容器也能够,原理同Vector是同样的,就是给全部的方法套上一层synchronized。

ArrayList用来作队列合适么?

队列通常是FIFO(先入先出)的,若是用ArrayList作队列,就须要在数组尾部追加数据,数组头部删除数组,反过来也能够。

可是不管如何总会有一个操做会涉及到数组的数据搬迁,这个是比较耗费性能的。

结论:ArrayList不适合作队列。

那数组适合用来作队列么?

这个女人是魔鬼么?不过仍是得微笑面对!

数组是很是合适的。

好比ArrayBlockingQueue内部实现就是一个环形队列,它是一个定长队列,内部是用一个定长数组来实现的。

另外著名的Disruptor开源Library也是用环形数组来实现的超高性能队列,具体原理不作解释,比较复杂。

简单点说就是使用两个偏移量来标记数组的读位置和写位置,若是超过长度就折回到数组开头,前提是它们是定长数组。

ArrayList的遍历和LinkedList遍历性能比较如何?

论遍历ArrayList要比LinkedList快得多,ArrayList遍历最大的优点在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片断,能够大幅下降读取内存的性能开销。

能跟我聊一下LinkedList相关的东西么?

能够呀,否则今每天色已晚,否则咱们下次再聊?

好吧,每次你都找借口,下次能够集合最后章节了,咱们好好聊聊,你好好准备吧。

总结

ArrayList就是动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了动态的增长和减小元素,实现了ICollection和IList接口,灵活的设置数组的大小等好处。

面试里面问的时候没HashMap,ConcurrentHashMap啥的这么常问,可是也有必定几率问到的,仍是那句话,不打没把握的仗

咱们在源码阅读过程当中,不须要所有都读懂,须要作的就是读懂核心的源码,加深本身对概念的理解就行了,用的时候不至于啥都不知道,不要为了用而用就行了。

ArrayList经常使用的方法总结

  • boolean add(E e)

将指定的元素添加到此列表的尾部。

  • void add(int index, E element)

将指定的元素插入此列表中的指定位置。

  • boolean addAll(Collection c)

按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的全部元素添加到此列表的尾部。

  • boolean addAll(int index, Collection c)

从指定的位置开始,将指定 collection 中的全部元素插入到此列表中。

  • void clear()

移除此列表中的全部元素。

  • Object clone()

返回此 ArrayList 实例的浅表副本。

  • boolean contains(Object o)

若是此列表中包含指定的元素,则返回 true。

  • void ensureCapacity(int minCapacity)

若有必要,增长此 ArrayList 实例的容量,以确保它至少可以容纳最小容量参数所指定的元素数。

  • E get(int index)

返回此列表中指定位置上的元素。

  • int indexOf(Object o)

返回此列表中首次出现的指定元素的索引,或若是此列表不包含元素,则返回 -1。

  • boolean isEmpty()

若是此列表中没有元素,则返回 true

  • int lastIndexOf(Object o)

返回此列表中最后一次出现的指定元素的索引,或若是此列表不包含索引,则返回 -1。

  • E remove(int index)

移除此列表中指定位置上的元素。

  • boolean remove(Object o)

移除此列表中首次出现的指定元素(若是存在)。

  • protected void removeRange(int fromIndex, int toIndex)

移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的全部元素。

  • E set(int index, E element)

用指定的元素替代此列表中指定位置上的元素。

  • int size()

返回此列表中的元素数。

  • Object[] toArray()

按适当顺序(从第一个到最后一个元素)返回包含此列表中全部元素的数组。

  • T[] toArray(T[] a)

按适当顺序(从第一个到最后一个元素)返回包含此列表中全部元素的数组;返回数组的运行时类型是指定数组的运行时类型。

  • void trimToSize()

将此 ArrayList 实例的容量调整为列表的当前大小。

点关注,不迷路

好了各位,以上就是这篇文章的所有内容了,能看到这里的人呀,都是人才

我后面会每周都更新几篇一线互联网大厂面试和经常使用技术栈相关的文章,很是感谢人才们能看到这里,若是这个文章写得还不错,以为「敖丙」我有点东西的话 求点赞👍 求关注❤️ 求分享👥 对暖男我来讲真的 很是有用!!!

白嫖很差,创做不易,各位的支持和承认,就是我创做的最大动力,咱们下篇文章见!

敖丙 | 文 【原创】

若是本篇博客有任何错误,请批评指教,不胜感激 !


文章每周持续更新,能够微信搜索「 三太子敖丙 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub github.com/JavaFamily 已经收录,有一线大厂面试点思惟导图,也整理了不少个人文档,欢迎Star和完善,你们面试能够参照考点复习,但愿咱们一块儿有点东西。

相关文章
相关标签/搜索