可以关注我的微信公众号:xiaobei109208,每周一篇技术分享哦。
前言
Foreword
我想了半天,文章的开头不知从何起笔,你说扯不扯?
可能因为这是第一篇从公众号写的技术分享,我担心最后写成了段子。。。
写作目的
THIS IS TITLE
因为是第一篇所以废话有点多,但没关系,后面的技术内容我也会尽量使用段子的方式表述,毕竟技术是枯燥无味的,不然,我们上学时睡觉那叫一个香呢~
写作目的很明确,说技术分享那是大爱,说给自己加深影响并留以备份好像更合适。最重要的是万一哪个技术大牛看到了这篇文章,发现不足之处,对我加以批评,蹦起来前空翻踹我一jio,并给我指正错误,我可能会兴奋的给大家表演前空翻后空翻360度旋转大劈叉,这都是财富啊~~
此处进入主题。。。就是这么突然
ArrayList以数组实现的存储方式
THIS IS TITLE
ArrayList在平时使用中多的不能再多了,因为List装一切当然还有Map,此篇List站C位。与它类似的还有LinkedList,LinkedList底层是以链表的存储方式,两者常用,区别如此:
1. 数据结构的实现:ArrayList是动态数组的数据结构实现,而LinkedList是双向链表的数据结构实现。
2. 随机访问效率:ArrayList查找速度比LinkedList快,因为LinkedList是线性的数据存储方式(这里可能会有不解的人,上面不是说它是双向链表的吗?怎么又是线性的了,好了,看官消消火,也不用再去百度区别,我来说明:链表原先是C/C++的概念,是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的数据以外,还存储其下一个存储单元的地址,这样每次查询的时候通过某个存储单元的下一个地址寻找其后面的那个存储单元),所以需要移动指针从前往后依次查找。
3. 增加和删除效率:在非首尾新增或删除数据,LinkedList要比ArrayList效率要高,因为ArrayList增删操作影响数组内其它下标。
总结:频繁读取数据采用ArrayList,插入删除采用LinkedList
ArrayList特点:查询效率高,插入删除效率低,非线程安全
ArrayList源码解析
THIS IS TITLE
还得提一点,很多面试官经常会问,为啥它是非线程安全还要使用?
线程安全的也有,Vector线程安全,但是!!!ArrayList确实是使用率最高的,就因为它查询效率快。世界万物皆有利弊,道理是一样的,不存在任何一种集合工具,满足全部优点,如果有,那就当我没说过,(认怂保平安)至少现在来说是没有的,所有线程安全的数据结构都会有效率的问题,因为会有线程同步,也就是很多方法上的synchronized。
技术最忌讳的就是为了用而用,就像设计模式一样,很多为了用而用的项目最后维护起来,简直能自闭。。。。
源码解析:
通过无参构造,给底层Object[] elementData赋予初始值,数组容量为0,只有在add的时候才会分配默认的初始值10
同样再看一下有参数构造方法,你觉得你给了initialCapacity大小值,会不会初始化大小?
答案是不会的,我和一些不了解的同学一样,一度觉得自己是智障,什么情况?提供了有参构造,给了指定大小值,竟然不会初始化,并且只有结合set方法,才会抛出异常。算是Java Bug的一个经典了,了解一下就好。
ArrayList扩容机制:
既然初始化数组的容量大小指定是10,那么当添加的数据长度超过10,它是怎么实现自动扩容的呢?
举个栗子🌰:一个for循环添加了第10位数后,再添加第11位的时候发现数组已经满了,
它的实现方式:通过位运算符,重新定义了一个数组长度为 10 + 10/2的数组,也就是数组长度为15,并且把原来的数据原封不动拷贝到新的数组上。
这也是这个核心部分,其它源码一目了然,阅读理解满分卷,so easy~~。
在上面也和LinkedList比较了,它在增删上效率低于Linked List,问题出在哪里呢?
在进行扩容的时候,jdk8之前和之后还是有区别的,jdk8真的算是一个里程碑了,做了很多 的优化,像上图中的位运算符就是jdk8做的新优化,以至于效率提高了。
新增有指定的index新增,也有直接新增,在指定index新增的时候,看一下代码实现:
先进行数组校验,校验完之后也是数组copy,就是arraycopy,再次举个栗子🌰
同样拿图说话
现在在index为4的位置新增一条数据“AA“,索引从0开始,它复制了一个数组,从index=4开始,然后把它放在了index + 1的位置,给新人腾位置,真是人走茶凉~~
然后就变成这样了
就这样反反复复,你说说如果是几百几千或者更多的数据,不断的复制,再加上扩容,不慢都没有道理。
那再看是如何实现删除的,删除一定就慢吗?
其实删除慢不慢取决于被删除的元素离末端有多远,来看一下remove的源码,可以看到他其实还是一个copy的动作,简单来说就是复制,然后覆盖要被删除的元素,营造出一种被删除的感jio~
举个栗子🌰:删除index为4的元素
它的做法,是从index 4 + 1 开始复制后面的元素,把该位置的元素覆盖,所以说,眼见为实,耳听为虚是有道理的。
最后看一下ArrayList的结构导图:
但是!!!这里有两个问题,值得探讨一下,知其然先知其所以然
第1:为什么数组容量初始值是:10,而不是8不是其它的呢?
第2:为什么数组最大长度值是:Integer.MAX_VALUE - 8,而不是减5减6?
其实ArrayList是最简单的一种数据结构,只不过有几点拿出来还是值得去思考一下的,就像上述的这两个问题。
第1个:其实没有找到具体的解释,翻阅各大博客,但是从敖丙的博客中了解到了一点点
据说是因为sun的程序员对一系列广泛使用的程序代码进行了调研,结果就是10这个长度的数组是最常用的最有效率的。也有说就是随便起的一个数字,8个12个都没什么区别,只是因为10这个数组比较的圆满而已。
看完以后,我40米的大刀抽出来,对着空气砍了半个小时,仿佛把我的智商按住地上摩擦,这个就和做hashMap的那个大佬在jdk7之前,定义新的Entry节点插入链表采用头插法,原因是因为他觉得后来插入的被查询的可能性较大,我TM。。。。。自闭了。。。。
但是没办法,他们说啥就是啥。
第2个问题:也找了相关资料,能够直接说服我的那种,jdk8版本中最大是Integer.MAX_VALUE - 2,但是这里-8,保留了6个字节大小,数组作为一个对象,需要一定的内存存储对象头的信息,对象头信息最大占用内存不可超过8字节。并且这个静态常量的注释也说明了,如果数组长度过大,可能会出现两种错误,1:堆区内存不足,2:超过JVM最大限制
声明:部分文字表达参考作者为三太子敖丙的文章
知其然知其所以然,下句其实是,你知道的越多,你不知道的就越多
各位好,我是李小北,一名励志做全栈的程序员。
END
来源:网络(侵删)
图片来源:网络(侵删)