文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!)

一、写在前面

这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList、HashMap及其对应的线程安全实现,此文章作为自己相关学习的一个小结,记录学习成果的同时,也希望对有缘的朋友提供些许帮助。

当然,能力所限,难免有纰漏,希望发现的朋友能够予以指出,不胜感激,以免误导了大家!

二、稳扎稳打过源码

首先,是源码内部的成员变量定义以及构造方法:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 参数项

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 构造方法

 进阶分析,集合常用操作,先从简单入手:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 按位次获取单个集合元素

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 按位次修改单个集合元素

  如前所述,单个元素按位次获取操作很简单,只要你了解到arrayList内部是数组存放数据,那上述操作完全可以想得到。

  下面继续,集合的添加和删除操作,没看过源码的朋友可以思考下,添加和删除操作同上述两个操作主要的区别在哪里?为什么会复杂些?

  因为添加元素的时候可能位置不够嘛,那怎么办?位置不够就加位置,也就是所谓的“扩容”!我们自己思考下实现思路,然后同作者的思路对比下:

  1.上面我们提到,默认构造方法使用的lazy_init模式,添加元素时才init。那第一步判断是否需要init,需要则init。

  2.判断是否需要扩容(通俗点说就是当前数组有没有空位置?)需要就扩容(创建个容量更大的新数组),但是扩容也不是无限的,超过上限就报错(Integer.Max_Value),并把原数据copy到新数组,否则什么都不做

  3.在指定位置添加元素,并size++

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 单个添加集合元素

  对比后发现,总体思路是没问题的,但是作者为什么要加入下面这样的容量上限处理逻辑?朋友们可以思考下,ArrayList的容量上限到底是Integer.MAX_VALUE还是MAX_ARRAY_SIZE?

  答案是:Integer.MAX_VALUE,依然还是它,并不是源码中作者定义的MAX_ARRAY_SIZE,那就引申出一个问题了-MAX_ARRAY_SIZE存在的意义。

  我的理解是,这个上限的存在,减小了内存溢出的概率。首先注释中也提到了“Some VMs reserve some header words in an array”,意思就是有些虚拟机把头信息保留在数组中,毫无疑问保存信息是要占空间的,也就是我实际数组空间可能是不足Integer.MAX_VALUE的,作者应该是这样的思路(我猜的~)“我可以基本确定实际空间不会小于MAX_ARRAY_SIZE(可能是分析了主流虚拟机的实现而得出的结论),因此设置个阈值,来确保不会内存溢出,但如果这个容量还是不够,那把剩下的风险很高的8也给你好了,至于是不是会溢出,天知道,反正我已经尽力了!

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);private static int hugeCapacity(int minCapacity) {        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 单个删除元素

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 批量删除

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 序列化与反序列化

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 迭代器

三、 ArrayList使用建议

  1.当你需要确定长度的ArrayList时,推荐使用该长度初始化,理由是防止频繁扩容,扩容相对而言是对性能影响相当大的操作(请注意是相对而言,相对于常规的增删改查等操作)。

  2.谨慎使用clone、toArray,对于源对象和方法返回对象,数据修改操作会相互影响。他们最终都执行了System.arrayCopy(),都是“浅拷贝”:只copy了引用,所有对于其中一个引用的修改操作会传递到其他引用上。java的copy应该都是“浅拷贝”,测试如下:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 测试clone

  3.谨慎使用subList,深入分析后会发现,本质原因同上面类似,都是相同的引用,所以数据修改操作会相互影响,如下例子:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 测试subList

  4.细心的朋友可能发现了,arrayList没有提供对数组的构造方法,但是我们知道array->arrayList是比较常见的需求,那如何做呢?办法不止一种,选择你喜欢的即可,如下:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训 arrayToList

四、源码相关扩展(我能想到的可以深入思考的一些地方)

  1.快速失败(fail fast)机制:

  我们知道,ArrayList不是线程安全的集合类。意味着在并发操作时,很容易发生错误。按照常规思路,发现结果出错,抛出异常即可。但在实际应用中,我们并不满足于此,有没有一种检测机制,在并发操作前进行校验,提前告诉我们可能发生的错误呢?如你所料,就是我们提到的快速失败机制。它是如何实现的呢?其实很简单,我们定义一个计数器modCount,这个计数器记录所有集合结构变动的次数,比如增加两个元素就modCount+=2,再比如删除3个元素就modCount+=3,这个计数器只能增加不能减少,然后我们在执行一些需要快速失败的操作时(比如:迭代器、序列化等等),执行之前记录下当前modCount为expectedModCount,在适当的时候判断expectedModCount、modCount是否相等就可以判断这期间是否有过集合结构的变动。

  2.lambda表达式:读源码我们发现,ArrayList中加入了许多支持lambda的方法,作为JDK8的亮点之一,应该能够熟练使用,然后再详细分析lambda的实现机制我觉得也很有意思。

  3.jdk中常用JNI方法的实现:上文我们在对clone方法做分析的时候,最终只分析到Object的native方法,我觉得有机会去看下常用的一些native方法的实现也是很有意思的,待研究。

五、总结一下

老实说,分析完ArrayList发现,所花的时间大出我预计,我一度认为我已经理解的很透彻了,但是在写这篇文章的途中我又把差不多一半的源码回顾了一遍(这真是个悲伤的故事),不过不管怎么说,这是个好的开始,后面一篇解析hashMap。


延伸阅读

向着Java前进-Java培训,做最负责任的教育,学习改变命运,软件学习,再就业,大学生如何就业,帮大学生找到好工作,lphotoshop培训,电脑培训,电脑维修培训,移动软件开发培训,网站设计培训,网站建设培训向着Java前进