jdk源码ArrayList分析基于JDK10时间20180827

分析目的

        接触了 java 两年,虽然在2015年阅读了一遍 Collection 接口下面的各种实现。但是最近还是想重温一下 jdk 的源代码,同时也想用笔记的方式记录和分享一下学习历程。

        目前初步想把 Collection 和 Map 的源代码都重新阅读分析一遍,以便加深印象理解。其实这也是面试中最常见的问题之一,例如ArratList是如何实现的,ArrayList 和 LinkedList 有什么区别,常见数组扩容的深拷贝和浅拷贝等知识点。本文先讲 ArrayList ,主要围绕着实现原理,然后从构造函数,到增删是如何实现的。

简介:

    1. ArrayList 是基于数组实现的一个队列,因为基于数组,但是相对来说,他比数组更为灵活,因为可以动态扩容,但是效率不如数组(知识点)。

    2. ArrayList 实现了 RandmoAccess 接口,即提供了随机访问功能。RandmoAccess 是 java 中用来被List 实现,为 List 提供快速访问功能的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。其实就是快速从索引来访问。

    3.ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。注意:这里有个知识点是java的深拷贝和浅拷贝,这里需要追踪到Arrays.copy方法。不过这个方法是个native的,需要最终到c++的实现。这点应该也是大厂的面试题之一(知识点)。

    4.ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

    5.ArrayList是线程不安全的,这里经常有对比的就是同一家族下的vector(知识点)。

        首先看结构:

构造函数

1.无参构造

    public ArrayList() {

            this.elementData =DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

       }

        这里说明,默认构造容量为10的一个arraylist,但是要注意,实际上这里的长度是空的,继续往下看,稍后分析。

2.初始化指定容量的List

首先如果制定的长度大于0,那么初始化一个指定构造函数内,参数大小容量的 object 数组。

如果参数为0,那么设置list置为长度为0的 object 数组。


如果指定长度小于0,那么 new IllegalArgumentException()异常。

3.接收一个 Collection 作为参数,构造一个List(),这里也说明,只要实现了 Collection 接口的容器,都可以作为参数,传递给 ArrayList() ;

如果当前集合长度不为0,那么调用 Arrays.copyOf()方法,将参数,复制给当前数组。

否则,将当前数组赋值为空的 object 数组。



这是基础的三个构造函数。

add方法

        add 方法,共有两个 public 方法,供我们调用。方法一接受指定类型的一个元素,方法二在指定的元素索引处增加元素,然后后续的元素索引分别增加1。

方法1

        首先分析方法1,增加一个指定类型的元素。(这里也可以称为范型,是 jdk1.5 新增加的特性)。


        这里调用私有的 private add()方法,首先判断当前的size,和 List 内的元素长度是否相等,这里假如是第一次添加时,如果条件成立那么进行扩容,或者说是容器的长度存储到数组容量的当前最大索引数时。

接下来看扩容操作,grow()方法做了哪些事情。

    首先当前容器的 size 增加1。

然后进行数组的 copy 方法,

但是 copy 方法要注意的就是另一个方法,newCapacity(),

        这里照应了,第一个无参的构造函数,是在添加元素时,才会选择初始化10个长度的数组,这也是优化代码的一个小技巧。同时 newCapacity()方法,返回新的数组长度。

    下面的return之前会判断长度是否为数组的最大长度-8,这里以前的jdk是没有-8操作的,后续可以解释这里为什么-8,当然也可以百度这个问题。最主要的就是要记住,ArrayList的最大长度就是Integer.MAX_VALUE-8,就是整型最大长度-8。

再扩容完之后,接下来介绍 Arrays.copyOf()方法。    



最终会跟到 native 方法,arraycopy()

arraycopy,共需要五个参数

Object src : 原数组

int srcPos : 从元数据的起始位置开始

Object dest : 目标数组

int destPos : 目标数组的开始起始位置

int length  : 要copy的数组的长度

例如 :

有一个数组数据 byte[]  srcBytes = new byte[]{1,2,3,4,5,6,7,8,9,10};  // 源数组

                                    byte[] destBytes = new byte[5]; // 目标数组

我们使用 System.arraycopy 进行转换 (copy) ,System.arrayCopy(srcBytes,0,destBytes ,0,5)

上面这段代码就是 : 创建一个一维空数组,数组的总长度为 10位,然后将srcBytes源数组中 从0位 到 第5位之间的数值 copy 到 destBytes 目标数组中,在目标数组的第0位开始放置.

那么这行代码的运行效果应该是 1,2,3,4,5 。

注意:这里 ArrayList 中目标数组的长度的长度,是永远大于源数组的,因为需要将源数组的所有元素copy到目标数组中,然后返回新的数组。

接下来,将新的元素填充返回新的数组结构中,然后 size+1 。如果添加中没有任何异常发生,则默认返回boolean 为 true 的结果。

方法2

        方法2为,添加元素在数组的指定索引位置。共有两个参数,参数1,int 值为 List 的索引,参数二,为需要添加的元素。

        这里可以理解为插队。比如你去火车站排队,假如你的车要走了,你很急需要插队。这时候你可以直接跑到队伍前面,这时候后面的所有人就都要往后排一个人出去。

add()

首先这里判断数组内的实际元素数量,和数组容量的size是否相等。如果相等,则进行扩容操作。和上面讲解的扩容操作,做了一件事情。

这里注意一点就是不要把,size 和 this.elementData.length 混淆。

比如:new object()[10] 

那么 size 为10,但是假如你add 5个元素之后,this.elementData.length 为5

        在前面的基础一些校验和判断相关操作完成后,开始真的数组添加操作,实际上就是这一句话做了实际的事情。

       这里的 arraycopy 就是说的插队操作,每个元素,从 elementData 的 index 需要插入的位置开始截取,然后从 index+1 处开始复制。在复制完之后,将新的元素插入到 index。

remove方法

    方法1    

.、

        首先 Objects.checkIndex(index, size); 这里要校验,删除的index是否合法,例如数组长度为10,你要删除的 index 为12,这里就会抛出异常。

然后将要删除的元素保存起来。在删除成功之后,会把旧的 value return 回去。

开始真正的删除动作

remove()

        这里继续调用 System.arraycopy()方法,从原来需要删除的 index+1 开始截取,复制到原来 index的位置。通俗点理解,就是排队时候,前面有一个人因为有事情走了,这样你们后面每个人则可以向前多走一个人的距离,然后队伍最后面设置为 null 。就完成了删除操作。

 方法2

        删除容器内指定的元素,

            首先这里 remove 的元素是否为空,如果为空。则找到数组内为空的元素,并且 beak 出去,,同时i变量记录下了索引。注意:这里也说明,ArrayList 是可以存储 null 的。其实在数组没有填充满时,存储的就是 null ,这里只是加以说明一下。此时,else 内只是找到相同的元素,同样返回索引。然后进行删除。

判断

接下来看具体的删除操作


fastRemove

这里的 fastRemove(快速删除),是怎样快速删除的呢?具体看代码来分析。


        其实是同样的 System.arraycopy() 方法,将当前元素的后面所有元素,从当前指定索引数开始复制。并将最后一个元素内从置为null。

总结:

        ArrayList 大部分都围绕着 arraycopy 方法来实现。其实就是对数组的一种快速操作的方法。只要围绕着这一点,理解 ArrayList 其实还是不难的,只要对方法进行足够细致的拆分。这样就像堆积木一样,得以足够的复用。这里我也只是做到抛砖引玉的作用,因为还有很多的方法没有讲解到,但是道理都是相似的。一切都是围绕着一个主题,就是数组的快速操作,这样理解起来就不难了。

        想要学习的透彻,一定要自己动手根据理解写一份代码出来!!!


附注:

        我一直觉得自己是个小白,从自己的角度尽可能细致的去分析。希望能给大家还有我自己带来一些收获。

你可能感兴趣的:(jdk源码ArrayList分析基于JDK10时间20180827)