ArrayList扩容源码剖析

Posted 爱上口袋的天空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArrayList扩容源码剖析相关的知识,希望对你有一定的参考价值。

一、先看ArrayList的简单总结

1、ArrayList中维护了一个Object类型的数组elementData.

     transient Object[] elementData;(Object类型的数组说明什么类型都可以放)

     对象在序列化的时候,transient修饰的属性不会被序列化

2、当创建对象时,如果使用的是无参构造器,则初始elementData容量为0,其实就是一个空数组

3、当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适位置

4、当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。

5、如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity

6、如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍。


二、底层源码解析1

 1、我们使用下面的代码程序进行源码debug分析

package com.kgf.kgfjavalearning2021.collection;

import java.util.ArrayList;

public class ArrayListSource 

    public static void main(String[] args) 

        //使用无参构造创建ArrayList对象
        ArrayList arrayList = new ArrayList();
        //使用for循环为list添加1-10数据
        for (int i = 0; i < 10; i++) 
            arrayList.add(i);
        
        //使用for循环为list添加11-15数据
        for (int i = 11; i <= 15; i++) 
            arrayList.add(i);
        
        arrayList.add(100);
        arrayList.add(200);
        arrayList.add(null);
    

2、首先进行下面创建无参构造器的源码分析

进入内部源码查看:

 3、当第一次向ArrayList集合中添加元素时

 点击进入add方法:

下面我们进入ensureCapacityInternal方法,这个方法是用来确认底层数组大小是否够用来存放数据,如果不够就需要进行扩容:

 可以发现上面会进行判断数组是不是一个空数组,因为第一次无参构造创建ArrayList集合底层就是一个空数组,所以如果发现是一个空数组,那么扩容elementData大小为10

 下面还需要进入ensureExplicitCapacity方法去判断是否需要真的扩容:

 上面的含义就是ArrayList是空间不够了才扩容的,假设现在插入第11个元素,但是只有10个空间,就触发grow扩容

4、下面分析grow扩容方法

上面的代码逻辑分析:

1)、首先获取elementData数组的长度oldCapacity

2)、oldCapacity >> 1表示就是oldCapacity除以2,所以newCapacity就等于oldCapacity
       的1.5倍

3)、但是第一次的时候oldCapacity等于0,所以不会走这个1.5倍的扩容机制

 4)、判断newCapacity是否大于MAX_ARRAY_SIZE,如果大于走hugeCapacity方法

 下面进入hugeCapacity方法看看:

可以发现最大就是Integer.MAX_VALUE 

 5)、最后就是真正的开始扩容了

 

 并且注意:Arrays.copyOf这个方法在扩容的时候会保留原来的数据,例如:

String[] arra = ["1","2"];

使用Arrays.copyOf扩容到5个,结果如下:

 String[] arra = ["1","2",null,null,null];

5、最后回到add方法进行添加元素,因为此时经过扩容,大小已经够了

注意,注意,注意,Idea 默认情况下,Debug 显示的数据是简化后的(就是看不到全部数据),如果希望看到完整的数据需要做以下设置.


三、底层源码解析2

1、下面我们把上面的测试代码程序设置一个初始化集合大小

package com.kgf.kgfjavalearning2021.collection;

import java.util.ArrayList;

public class ArrayListSource 

    public static void main(String[] args) 

        //使用无参构造创建ArrayList对象
        ArrayList arrayList = new ArrayList(8);
        //使用for循环为list添加1-10数据
        for (int i = 1; i < 11; i++) 
            arrayList.add(i);
        
        //使用for循环为list添加11-15数据
        for (int i = 11; i <= 15; i++) 
            arrayList.add(i);
        
        arrayList.add(100);
        arrayList.add(200);
        arrayList.add(null);
    

 2、下面我们进行debug查看源码

 创建ArrayList对象完成,我们看一下第一次添加元素时流程

 

 下面我们看一下当添加的元素超过8个的时候扩容方式:

 

 

以上是关于ArrayList扩容源码剖析的主要内容,如果未能解决你的问题,请参考以下文章

ArrayList 从源码角度剖析底层原理

ArrayList 扩容机制剖析

JavaSE面试题——基于JDK1.8中Vector的实现原理(源码剖析)

arraylist源码解析

Java Review - ArrayList 源码解读

Java基础干货ArrayList源码剖析