java ArrayList源码分析(深度讲解)

目录

一、前言

二、ArrayList类简介

三、ArrayList类的底层实现

四、ArrayList类的源码解读(断点调试)

0.准备工作 :

1.空参构造——分步骤Debug(详细阐释)(重要)

0°开始Debug。

1°初始化底层elementData数组为空数组。

2°对add方法中的实参进行自动装箱。

3°进入add方法底层。

4°进入grow方法。

5°进入grow方法底层。

6°逐层返回,第一次扩容elementData数组完毕(0 ——> 10)。

7°向集合添加第二个元素(不需要扩容)。

8°将集合中的元素添加到10个元素。(第一个临界点)

9°集合的第二次扩容开始。

10°集合的第二次扩容结束。

11°将集合中的元素添加到15个。(第二个临界点)

12°集合的第三次扩容开始。

13°集合的第三次扩容结束。

2.带参构造——分步骤Debug(详细阐释)

0°前言 :

1°开始Debug。

2°集合的第一次扩容(初始化)。

3°向集合中添加第一个元素。

4°将集合中的元素添加到4个。(第一个临界点)

5°集合的第二次扩容。

6°将集合中的元素添加到6个。(第二个临界点)

7°集合的第三次扩容。

五、总结


一、前言

        大家好,本篇博文是对单列集合List的实现类ArrayList的内容补充。之前在List集合的万字详解篇,我们只是拿ArrayList演示了List接口中的常用方法,并没有对它进行深究。但这正是我们今天要做的内容。
        up会利用断点调试(Debug)来一步一步地给大家剖析ArrayList底层的扩容机制到底是如何实现的。 重点是空参构造器构造ArrayList对象后,底层扩容机制的详细实现
        注意 : ①解读源码需要扎实的基础,比较适合希望深究的同学; 不要眼高手低,看会了不代表你会了,自己能全程Debug下来才算有收获; 点击文章的侧边栏目录或者前面的目录可以进行跳转。 本篇博文对ArrayList源码的解读基于JDK17.0的版本,虽然不是主流的JDK8.0,但是经过对比不难发现其底层原理大同小异,所以,就算你用的不是高版本的JDK,本文也对打牢你的基础有一定帮助。良工不示人以朴。 感谢阅读!

二、ArrayList类简介

                ArrayList类是单列集合List接口的一个实现类,它的本质是一个可以动态修改的数组。ArrayList属于java.base模块,java.util包下,如下图所示 :

                我们再来看看ArrayList类的类图,如下图所示 :

                可以看到,ArrayList的类图还是比较复杂的。但是几个重要的点我们应该看到:ArrayList类直接继承自AbstractList抽象类,并且实现了List,Serializable等接口。Serializable接口相信大家已经不陌生了吧,实现该接口使得ArrayList类的对象可以串行化,串行化后,对象可以进行网络传输,也可以保存到文件。


三、ArrayList类的底层实现

                1.ArrayList类在底层是由数组来实现的,ArrayList类源码中维护了一个Object类型的数组elementData,用于存储ArrayList集合中的元素。

                关于transient关键字,transient本身是转瞬即逝的意思,如下 :

                被transient关键字修饰的程序元素不可被序列化。

                2.当我们使用空参构造来创建ArrayList类对象时,则elementData数组的初始容量为0,第一次添加元素时,将该数组扩容为10,如需再次扩容,则将elementData数组的当前容量扩容为1.5倍

                3.如果使用指定数组初始容量大小的带参构造来创建ArrayList类对象,则elementData数组的初始容量即为传入形参的指定容量,如果需要扩容,则直接将该数组当前容量扩容至1.5倍


四、ArrayList类的源码解读(断点调试)

        0.准备工作 :

                up以一下代码为演示,来进行Debug操作,(分别在第13行和第20行设置断点)代码如下 :


package csdn.knowledge.api_tools.gather.list;

import java.util.ArrayList;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class ArrayList_Demo {
    public static void main(String[] args) {
    //演示 : Debug ArrayList空参构造,以及数组扩容的全流程。
        //1.通过空参构造创建一个ArrayList类对象
        ArrayList arrayList = new ArrayList();
        System.out.println("使用空参构造创建的集合对象 = " + arrayList);

        //2.利用for循环向集合对象中添加10个元素。(0~9)
        for (int i = 0; i < 10; i++) {
            arrayList.add(i);   //此处有自动装箱
        }
        System.out.println("添加十个元素后,当前集合 = " + arrayList);

        //3.如果按照我们的理论,在向集合对象中添加第11个元素时,底层的数组需要扩容。(1.5倍)
        arrayList.add("这是集合的第十一个元素捏.");
        arrayList.add("这是集合的第十二个元素捏.");
        arrayList.add("这是集合的第十三个元素捏.");
        arrayList.add("这是集合的第十四个元素捏.");
        arrayList.add("这是集合的第十五个元素捏.");

        //4.再次测试ArrayList类底层数组的扩容机制。    (10 ---> 15 ---> 22)
        arrayList.add("这是集合的第十六个元素捏.");
        System.out.println("添加十六个元素后,当前集合 = " + arrayList);
    }
}

        1.空参构造——分步骤Debug(详细阐释)(重要)

            0°开始Debug。

                首先,我们进入Debug界面,并在无参构造调用行(此处为第13行),跳入ArrayList类无参构造,如下GIF图所示 :

            1°初始化底层elementData数组为空数组。

                跳入ArrayList无参构造,我们可以看到,它将用于存储集合元素的elementData数组进行了初始化。等号后面的这一大堆直译过来就是 "默认容量空的elementData数组" ,可以根据Ctrl + b/B查看ArrayList中该常量的源码,如下 :

                可以看到,这个所谓的 "默认容量空的elementData数组" 名副其实,确实真是一个空的数组,而且与ArrayList中用于存储集合元素的elementData数组一样都是Object类型。
                接下来,我们跳出这个无参构造,进入for循环,并跳入第一个元素的add方法

            2°对add方法中的实参进行自动装箱。

                第一次跳入add方法,会跳到valueOf方法中,对要添加的int类型进行装箱操作。如下图所示 :

                我们不用管他,直接选择跳出,并准备第二次跳入add方法。

                第一次跳出add方法后,该行代码仍然标注着高亮,表示此行代码还未执行完毕。我们第二次跳入add方法。

            3°进入add方法底层。

                第二次跳入add方法,我们来到了真的"add"方法,如下 :

                其中,形参列表的"e"代表了你要添加的元素,根据我们上面给出的代码,for循环要向集合对象中添加0~9这十个元素,这是for循环第一次循环,要添加元素0,因此可以看到,此时 e = 0。
                modCount属性用于保存你修改集合的次数,用来防止有多个线程修改它,多个线程修改时会抛出异常这里我们不用管它。
                可以看到,跳入的add方法中,还调用了一个形参列表不一样的"add"方法,我们暂时将这个"add方法中的add方法"称为内层add方法内层add方法传入了三个形参,分别是"当前要添加的元素(此时e = 0),"初始化后的空数组elementData", 以及"当前集合中的元素个数size"。显然,内层add方法更加底层,当然,我们要追进去看看。

            4°进入grow方法。

                更底层的add方法(内层add方法),如下 :

                可以看到,内层add方法中首先是一个if条件语句的判断,if条件语句结束后,才将e(当前e = 0)添加到了elementData数组中,并且为size属性 + 1(表示集合中的元素个数从0 --> 1,多了1个)。

                但是,要知道我们的elementData数组之前可是初始化为了一个空数组阿,是啥都放不下的。显然,它在if条件语句中被动了手脚。我们来仔细看看这个if条件语句,判断条件是"当前集合中的元素个数是否等于数组的大小(长度)",啥意思呢?就是说咱们现在不是正要向集合中添加元素么——(前面我们也看了,向ArrayList集合中添加元素,底层其实就是向ArrayList类中的elementData数组中添加元素)——如果判断条件成立,说明当前集合的元素个数与底层elementData数组的大小(长度)相等,也就是说数组已经满了,再想添加元素就要扩容🌶!

                我们再回到实际,因为我们正在添加第一个元素,所以目前集合中元素的个数 = 0(即size等于0);底层数组为空,所以目前数组的长度 = 0(即elementData.length等于0),0 = 0,满足判断条件。所以,肯定要进入这个grow方法。grow方法,见名知意,就是对数组进行扩容的方法。"elementData = grow();",显然grow方法最终是返回一个Object类型的数组,这样才能赋值给elementData数组(其实就是更改了elementData的引用)。我们也是一路高歌,继续跳入grow方法中看看是咋回事。如下图所示 :

                果然,grow方法返回了一个Object类型的数组。不过比较操蛋的是grow方法与前面的add方法类似,都™长了层包皮,没关系,我们继续追进去看看。(注意 : 既然外层的grow方法是返回Object类型的数组,说明return后面的grow方法肯定要是返回一个Object类型的数组。不过,内层的grow方法传入了一个形参是size + 1,size表示当前集合中元素的个数,第一个元素还没加进去,所以此时size + 1 = 0 + 1 = 1.)

            5°进入grow方法底层。

                内层的grow方法如下图所示 :

                是不是傻眼了😂?什么玩意儿!别急,不要因为走得太远而忘记了我们为什么出发。我们一路追追追,只有一个目的——elementData数组目前是个空数组,要添加第一个元素0进入,必须先扩容,而内层grow方法要返回一个Object类型的数组。所以,现在我们就找内层grow方法中有扩容操作的代码就行,别的暂时不看!

                可以看到,首先第一条语句,是把当前数组的长度(0)赋值给了oldCapacity变量,这条语句有啥用呢,我们暂时不管。继续往下看,有一个if-else的多重条件语句,判断条件是"当前数组的长度是否大于0,或当前数组是否不为空",显然,elementData目前还是个空数组呢, 长度就是0,两个条件均不满足,于是if语句中的内容现在不执行,直接跳到else语句。

                else语句中,注意观察,出现了return语句!显然,这里很可能就是返回Object类型数组的地方。我们来看看是不是。

                返回的Object数组的长度,是调用Math类的max函数的返回值,我们之前在常用类中讲过Math类,max函数可以返回两个数中的较大值。第一个数"DEFAULT_CAPACITY",直译过来就是"默认的容量",其实不用我说你们也能猜出来,和我们一开始初始化elementData数组时遇到的那"一大堆"一个拉撒,一丘之貉,这里也不追进去看了,但是注意,当我们鼠标悬停在这个常量上面是,会显式它的值 = 10第二个数是minCapacity(最小容量),这个变量是哪里来的呢?欸,你往上翻翻看,不就是内层grow函数的形参么!那这个形参是哪儿来的呢?欸,瞧你这记性,翻到上面外层grow函数,可以看到当时我们传入的是(size + 1),即0 + 1, 等于 1。minCapacity表示——要把该元素添加进去,所需的数组的最小长度,我们这才添加第一个元素阿,那minCapacity当然是1了。那两个数中,显然10 > 1,内层grow函数最终返回的就是"new Object[10]"。

            6°逐层返回,第一次扩容elementData数组完毕(0 ——> 10)。

                然后,内层grow函数执行完毕,返回外层grow函数,如下 :

                然后,再返回内层add函数,如下 :

                接着,内层add函数中,if条件语句中的方法将要执行完毕。

                可以看到,右上角的提示信息中,已经由原来的"Object[0]" 变成了"Object[10]",这也可以验证我们上文"ArrayList类的底层实现"中提到的——当我们使用空参构造来创建ArrayList类对象时,则elementData数组的初始容量为0,第一次添加元素时,将该数组扩容为10。

                接着往下执行,可以看到第一个元素0已经被添加进了elementData数组,如下图所示 :

                内层add函数执行完毕,返回外层add函数,如下 :

                可以看到,size(当前集合中元素的个数)已经由0变成了1,说明我们添加成功了。
                接着返回到我们的测试类中,如下 :

            7°向集合添加第二个元素(不需要扩容)。

                第一次扩容完成后,elementData数组的长度由0扩到了10,因此,for循环中后续几个元素的添加都不再需要扩容。以第二个元素的添加为例,up再来带大家过一遍,加深印象,当然,这次主要是体验一下流程,不会像第一次一样那么细了。

                如下图所示,随着循环变量i的自增,for的第一次循环顺利结束,第二次循环开始,向集合中添加第二个元素1 :

                可以看到,首先还是老规矩,先将int类型的数据1做了装箱处理。
                接着,我们再次跳入外层add方法,如下图所示 :

                注意看右上角,e(即我们要添加的元素)已经是1了,因为代码中的for循环,就是要向集合添加0~9这十个元素,现在是第二个元素1。继续往下执行,我们跳入内层add方法,如下图所示 :

                首先注意看右上角,s变量(当前集合中的元素个数)变成了1,因为我们之前已经添加过1个元素了么。执行内层add方法,if条件判断语句中,当前集合中元素的个数显然小于elementData数组的长度(1 < 10),因此不进入grow方法,而是直接将第二个元素放入elementData数组中索引为1的位置,同时再对size变量进行加1操作。如下图所示 :

                可以看到,第二个元素也已经成功添加到了elementData数组中,而且size变量也变成了2。
                之后,依然是先跳出内层add方法,再跳出外层add方法,一直跳回去,如下GIF所示 :

            8°将集合中的元素添加到10个元素。(第一个临界点)

                之后的8次循环(向集合中添加2~9这8个数),流程均与第二次循环相同,我们直接一笔带过,如下GIF图所示 :

            9°集合的第二次扩容开始。

                因为第一次对elementData数组进行扩容时,默认只从0扩到了10。而for循环结束后,我们已经向集合对象中添加了十个元素,即ArrayList底层的elementData数组已被装满。现在我们想添加第十一个元素,就需要对elementData数组进行第二次扩容了。

                我们还是先跳入add方法,看看会发生什么,如下图所示 :

                可以看到,由于从第十一个元素开始,均为String类型,String本身就是引用类型,因此不需要"装箱"的操作,而是直接跳到了外层add方法。同样地,我们继续跳入内层add方法,如下图所示 :

                注意看内层add方法中的if条件语句,IDEA已经给出了提示" = true",没错,当前集合中已有的元素个数s = 10,而当前elementData数组的长度elementData.length = 10,10 = 10,满足判断条件。因此这时候要第二次进入grow方法对数组进行扩容了。好,我们跳入外层grow方法,如下图所示 :

                注意看这时内层grow方法的实参,minCapacity : size + 1,即要想把第十一个元素放入集合中,所需集合的最小容量是size + 1 = 11,即所需elementData数组的最小长度是11。好的,接下来我们跳入内层grow方法,如下 :

                仍然是先将当前elementData数组的长度(10)赋值给了oldCapacity变量;接着,if条件语句,判断条件是"当前数组的长度是否大于0,或当前数组是否不为空数组",与我们第一次扩容不同,第一次是从0 ——> 10,现在elementData数组已经不为空了。因此,可以看到IDEA也是给出了提示" = true"。

                接着往下走,if语句体中,我们发现它由两部分构成。我们一步一步来看——

                首先,第一部分,是一个newCapacity局部变量的定义,见名知意,显然它代表了我们内层grow方法要返回的新数组的长度。而为newCapacity赋值的是另一个底层方法newLength,我们先不管它,继续往下看。

                第二部分,是一个return语句,显然,这个return语句中要返回扩容后的Object类型的新数组

                好的,框架清晰后,我们再看细节。既然第一部分要获取扩容后新数组的长度,那么显然我们是要追进去这个底层方法看看的。但是,进去之前,我们不妨先来看看它的形参列表一共传入了三个数——当前数组的长度(即旧数组长度);所需新数组的最小长度 - 旧数组长度(= 数组的最小增长量);旧数组右移1位。这里稍微说一下这个"oldCapacity >> 1"是什么意思,其实涉及到了C语言的一些基础——位运算。这里面涉及到了二进制的知识,当然我们也就废话少说,直接告诉你结论:"<<"表示左移,每左移1位相当于*2;">>"表示右移,每右移一位相当于/2。那么此处的"oldCapacity >> 1"就表示将旧数组的长度的值(10)右移一位,相当于10 / 2,= 5。因此,此处调用的newLength方法的实参就分别为10;1;5。

                OK,搞清楚这些后,我们追进去newLength方法一探究竟,如下图所示 :

                可以看到,newLength方法内部的结构还是比较清晰的 : 一个变量的赋值语句一个if-else的复合条件语句。我们一个一个来看。

                首先,"prefLength",直译过来就是"预设长度"的意思,见名知意,显然它和我们新数组的长度关系密切。继续看代码,为prefLength赋值的是"旧数组的长度 + 数组最小增长量和prefGrowth之间的最大值"。这个prefGrowth不知道大家能不能想到,前面我们传入三个实参的时候,第三个实参不是"oldCapacity >> 1"吗?欸,就是它!我们也可以将鼠标悬停在它上面,IDEA会显示出它的值,如下图所示 :

                可以看到,确实 = 5,和我们前面推理的一致。而minGrowth和prefGrowth一个1,一个5,肯定5大呀。所以,最终赋值给prefLength的值 = 10 + 5 = 15。欸,有没有发现,正好验证了我们前文中所提到的——当我们使用空参构造来创建ArrayList类对象时,如需再次扩容,则将elementData数组的当前容量扩容为1.5倍。估计到这里大家也能理解为什么是1.5倍了,就和前面那个位运算">> 1"有关,本身再加上它的一半,可不是1.5倍么。

                继续往下执行,if条件语句中,要判断新数组的长度是否合法,要满足大于0并且小于一个"一大堆"的玩意儿。我们还是将鼠标悬停在那"一大堆"上,看看它的值是多少,如下所示 :

                哎哟我去,行了,不用看了,咱就一个测试的数组,能跑那么大吗?
                🆗,newLength方法结束,返回内层grow方法。如下图所示 :

                还是那句话,不要因为走得太远而忘记了我们为什么出发。newLength方法只是为了获取新数组的长度,我们也进去看了,也知道是个啥了,就是旧数组的1.5倍。最后返回新数组,还是内层grow方法来完成。

                此处用到了Arrays的copyOf方法copyOf方法大家不用深究,只需要知道它的功能是将原数组中指定长度的内容拷贝到新数组中,并且,若指定的长度大于原数组长度,则多出来的部分以默认值填充,最终返回的是新数组。使用该方法扩容数组,可以在保留原数组中内容的同时,又达到扩容的目的。此时,我们新数组的长度15显然大于原数组长度10,因此我们猜测,最后返回的数组的效果就是[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, null, null, null, null]。(第11个元素未加入前)

                我们接着Debug,跳出内层grow方法,回到外层grow方法,如下所示 :

                接着再跳出外层grow方法,回到内层add方法,如下 :

                在内层add方法中,我们继续执行代码,将第十一个元素添加到elementData数组中,如下如所示 :

                可以看到,ArraList集合底层是先判断后扩容,先扩容后添加元素,且扩容后elementData数组的情况与我们预测的一模一样。多出的4个元素均以默认值null取代了。而且,当前集合中元素的个数也从10变成了11。

            10°集合的第二次扩容结束。

                接着,我们跳出内层add方法,一直跳到演示类的代码中,如下GIF图所示 :

                11°将集合中的元素添加到15个。(第二个临界点)

                之后的第12到第15个元素的添加,与第十一个元素的添加大同小异,只不过在内层的add方法中,我们不再进入grow方法(即数组不需要扩容),而是直接将元素添加到elementData数组中。因此,接下来4个元素的添加,我们一笔带过(有兴趣的小伙伴儿可以自己下去Debug一下),如下GIF演示图 :

            12°集合的第三次扩容开始。

                第二次扩容结束后,底层的elementData数组由10扩容到了15。而经过我们的一通操作过后,elementData数组又满了。现在我们想向集合中添加第16个元素,就要进行集合的第三次扩容。从第二次扩容开始,之后的每次扩容在底层都与第二次扩容原理一样,并且每次都扩容到当前集合容量的1.5倍。

                好的,同样地,我们先跳入外层add方法。如下 :

                接着,再跳入内层add方法。如下 :

                可以看到,if条件语句的判断中,又提示" = true"了(当前集合中元素的个数 = 底层elementData数组的长度 = 15),因此,如果想添加第十六个元素,我们还要再跳入外层grow方法中进行第三次扩容。如下 :

                显示当前集合中元素的个数为size = 15。则size + 1 = 16,表示当前需要的最小数组容量是16,继续跳入内层grow方法,如下:

                内层grow方法的if语句——判断条件也为true(旧数组容量大于0,elementData不是空数组)。注意,此时"oldCapacity >> 1"等价于"15 / 2"等于7(取整),OK,我们继续跳入newLength方法,如下  :

                可以看到,新数组的预设长度是22,恰好等于15 + 15 / 2 = 22,也再次印证我们之前的结论——扩容到1.5倍。

            13°集合的第三次扩容结束。

                好滴,接着我们再逐层返回,一直跳回到内层add方法中如下GIF图所示 :

                继续执行内层add方法中的代码,将第十六个元素放入elementData数组中(也就是放入ArrayList集合中),如下图所示 :

                可以看到,elementData数组的长度成功地由15扩容到了22,多出来的元素为Object引用类型的默认值null。
                接下来,我们逐层返回,一直返回到演示类中,如下GIF图所示 :

                🆗,无参构造的分步骤Debug演示,就到这里结束了。相信只要你把这一套流程吃透,自己可以Debug下来。那么其他情况下的扩容流程你也可以轻松举一反三了。

        2.带参构造——分步骤Debug(详细阐释)

            0°前言 :

                如果利用带参构造来初始化ArrayList对象,那么它底层的扩容机制,其实与无参构造初始化ArrayList对象时的大同小异。唯一不同的一点在于,使用带参构造初始化ArrayList对象,底层的elementData数组在一开始不会置空,而是将其初始化为调用带参构造时中实参指定的长度。之后的扩容流程与空参构造初始化对象时无异。因此,up这里就不会像之前空参构造时演示得那么细了。

                up以ArrayList_Demo2为例,代码如下 : (13行和22行设置断点


package csdn.knowledge.api_tools.gather.list;

import java.util.ArrayList;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class ArrayList_Demo2 {
    public static void main(String[] args) {
    //演示 : 测试通过带参构造初始化ArrayList集合时,其底层的数组扩容机制。
        //1.创建ArrayList集合对象
        ArrayList arrayList = new ArrayList(4);
        System.out.println("刚创建的集合 = " + arrayList);

        //2.向集合中添加元素(先来4个)
        for (int i = 0; i < 4; i++) {
            arrayList.add(i);       //此处涉及到了自动装箱。
        }

        //3.再次向集合中添加元素。(扩容:4 ——> 6)
        arrayList.add("这是第五个元素捏");
        arrayList.add("这是第六个元素捏");
        System.out.println("添加六个元素后,集合 = " + arrayList);

        //4.再次向集合中添加元素。(扩容:6 ——> 9)
        arrayList.add("这是第七个元素捏");
        System.out.println("添加七个元素后,集合 = " + arrayList);
    }
}

            1°开始Debug。

                如下图所示,进入Debug界面:

            2°集合的第一次扩容(初始化)。

                接着,我们跳入ArrayList的带参构造,如下图所示 :

                注意看,此时ArrayList带参构造的形参initialCapacity(初始化容量)就等于我们手动为它初始化的值 = 4。如下 :  

                可以看到,elementData数组被初始化为了长度为4的数组,4正是我们调用带参构造时指定的长度。其实,可以看到ArrayList类的该带参构造是一个if - else if - else的复合条件语句。因为我们指定的长度4 > 0,所以它直接进入if控制的语句中,即将一个长度为4的新数组赋值给了elementData数组(其实就是改变了elementData引用的指向)。如果带参构造传入的实参为0,它就会当作空参构造来执行。如果 < 0,就会抛出一个异常对象。

                接着,我们跳出带参构造。如下图所示 :

                注意,此时我们已经可以看到底层的elementData数组被初始化为4的长度。

            3°向集合中添加第一个元素。

                继续Debug,跳入for循环的add方法。同之前一样,这里我们要添加int类型的数据进入集合,底层会有装箱的过程,我们直接跳出即可,如下GIF图所示 :

                接着,我们再次跳入add方法中,来到外层add方法中,如下图所示 :

                可以看到,这不就是上面演示的外层add方法吗?是的,也就是说,从elementData数组的初始化后,带参构造方式创建的集合对象在扩容时,底层所有的操作都同无参构造一致
                因此,下面的演示up就多以GIF图来呈现了。其实只要上面那个无参的你会了,这个过一遍就🆗了。
                接下来,我们完成集合中第一个元素的添加,如下GIF图所示 :

            4°将集合中的元素添加到4个。(第一个临界点)

                由于底层的elementData数组初始化时长度为4。因此前四个元素的添加均无二致。我们一笔带过就好,如下GIF图演示 :

            5°集合的第二次扩容。

                当前集合的元素已达4个,要想向集合中添加第五个元素,需要再次进行扩容。扩容机制同上面的演示一样,这里不再赘述。如下GIF图演示 :

            6°将集合中的元素添加到6个。(第二个临界点)

                经过0 --> 4 --> 6的扩容后,集合容量已经到达了6。下面我们将集合中的元素添加到6个,使之达到第二个临界点,准备进行第三次扩容。如下GIF图所示 :

            7°集合的第三次扩容。

                经过两次扩容,集合的容量从0 ——> 4 ——> 6,那么第三次扩容,就应该是6的1.5倍 = 9了。我们来看看是不是,如下GIF图所示 :

                可以看到,第三次扩容后,elementData数组的长度确实从6扩到了9。如下图所示 :


 

五、总结

        🆗,以上就是我们ArrayLIst源码分析的全部内容了。其实,如果你看过JDK8.0的源码,不难发现两个在底层的实现上有着异曲同工之妙。当然,就up个人主观来看,两者的差异主要体现在了方法的形参和方法的位置上。话说回来,非常建议大家跟着up一起Debug一下,看源码,通过Debug去了解源码的结构,有助于提升大家对于源码的阅读力。 感谢阅读!
  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
java源码包实例源码JAVA开发源码50个合集: Ajax框架 ZK.rar Java图书馆管理系统源程序.rar Java图片倒影效果实例源码.rar Java图片翻折,将图像压扁.rar Java坦克大战网络对战版源代码.rar Java声音播放程序源代码.rar JAVA实现CLDC与MIDP底层编程的代码.rar Java实现HTTP连接与浏览,Java源码下载.rar Java实现的FTP连接与数据浏览程序.rar Java实现的放大镜效果附有源文件.rar Java实现的点对点短消息发送协议(smpp)开发包源码.rar Java实现的视频播放程序源码.rar Java实现移动的遮照效果.rar JAVA实现超级玛丽.zip Java实现跟踪鼠标运行坐标的源码.rar Java手机与计算机互发彩信源码.rar Java手机游戏大富翁源代码+注释.rar Java手机短信项目源码.rar Java扫雷源码.rar Java生成自定义控件源代码.rar Java调色板面板源代码.rar Java跳棋(基于SWT).rar Java通讯录手机版源码.rar Java鼠标拖拽功能.rar 乐趣大型购物系统.rar 可实现网上对战和人机对战.rar 基于BS结构的Java可视化工作流定制软件.rar 基于J2ME的Java游戏梦幻炸弹人源程序.rar 基于JAVA的ICQ系统.rar 基于Java的mp3播放器源代码.rar 基于Java的小型人事管理系统,带数据库.rar 基于JAVA的日程提醒簿.rar 基于Java的邮件服务器源程序.rar 基于MVC的Java资源管理器 v2.0.rar 基于smpp协议的Java点对点短信发送源码包.rar 季风进销存管理系统(JSP版).rar 客户管理系统 Alfresco Content Management.rar 家庭多媒体播放器.rar 局域网广播系统java源码.rar 开源Winzip压缩工具Java源码.rar 很不错的Java计算器.rar 很强的Java加密解密算法源码.rar 泡泡堂战车游戏JAVA源码.rar 简单模拟的J2ME潜艇大战源代码.rar 简单的注册与登录功能.rar 类似QQ的聊天软件JAVA源码(附设计文档).rar 进程通信.rar 连接postsql数据库的java代码.rar 附加数据库.rar 雷电游戏JAVA版源程序.rar

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyan_RA9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值