Java工程师不得不知的ArrayList源码-揭开ArrayList扩容机制的面纱

Java工程师不得不知的ArrayList源码-揭开ArrayList扩容机制的面纱

前言:

学集合,光知道各种集合类的用法是不够的

我们知道,集合都是基于对基础数据结构的封装,而数据结构与算法的地位我们也知道有多重要

面试中经常问的集合类源码,其实也算是考你对数据结构及其算法的把握程度了

在之前,我总结了Java提供的几大集合类的知识,看这篇文章的朋友可视情况回顾一下:

Linux的内核作者linus曾经说过,学习计算机的本质方法就是

RTFSC:Read the fucking source code

简单说一下ArrayList

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量

它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

如果你懂点数据结构,那么应该知道,一般线性表的顺序存储,插入删除元素的时间复杂度为O(n)求表长以及增加元素,取第 i 元素的时间复杂度为O(1)

  • ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能

  • ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问,这也是数组的特性,根据下标可以直接访问元素存放的位置

  • ArrayList 实现了Cloneable 接口即覆盖了函数 clone()能被克隆

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

ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList

ArrayList的扩容机制

下面是arraylist的单参数add方法,会触发扩容机制

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e; //elementData->类属性
        return true;
    }

ensureCapcityInternal:

/*minCapacity就是添加后的数组长度*/
private void ensureCapacityInternal(int minCapacity) {
    
    
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

elementData

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; 

根据注释,可以得到以下信息

  • elementData是一个数组缓冲区,存储ArrayList元素
  • 这个缓冲区的长度就是ArrayList的容量大小
  • 空的elementData定义为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  • 当有第一个元素进来时, DEFAULTCAPACITY_EMPTY_ELEMENTDATA将被拓展成DEFAULT_CAPACITY

DEFAULT_CAPACITY

				    /**
 * Default initial capacity.默认初始化容量为10,
 */
private static final int DEFAULT_CAPACITY = 10;

回到上面这段代码

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
  • 如果elementData为空,空的ArrayList添加第一个元素

    minCapacity选为为10

    即,第一个添加元素的容量将被初始化为10!

如果是不是添加第一个元素的情况,进入下一个方法:

ensureExplicitCapacity(minCapacity)

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;   //类属性  -->记录修改次数

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

看看modCount是什么东西

protected transient int modCount = 0;

关于transient:

Java中transient关键字的作用,简单地说,就是让某些被修饰的成员属性变量不被序列化

什么情况下考虑用反序列化transient:

1、类中的字段值可以根据其它字段推导出来,如一个长方形类有三个属性:长度、宽度、面积(示例而已,一般不会这样设计),那么在序列化的时候,面积这个属性就没必要被序列化了;

明显,Java认为这个字段无需写到硬盘

modCount看注释,大概是:

这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1。

回到代码

    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

如果minCapacity(新容量)大于缓存数组的长度,即超过之前扩容的容量了

调用grow(minCapacity),此时猜测,要进行真正的扩容了

grow()

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     * @param minCapacity the desired minimum capacity
     * Junsir注:扩容容量保证新容量至少能容纳新元素数量
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length; //拿到原来的容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量=旧容量*2  JunSir注:位运算右移一位等于乘2的一次方,位运算比直接*2效率更高
        
        //异常情况处理,不用关心
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
       
        //MAX_ARRAY_SIZE?先看下面注释相关联常量的内容
        //表示新容量已经或即将超负荷
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //hugeCapacity(minCapacity)?先看下面注释相关联方法的内容
            //Junsir注:返回newCapacity是Integer.MAX_VALUE或者MAX_ARRAY_SIZE取决于你的旧容量是否大于Java定义的最大数组长度 :
            newCapacity = hugeCapacity(minCapacity);
        //Arrays.copyOf?
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

/*相关联的常量-MAX_ARRAY_SIZE*/
    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	/*Junsir注: 定义了一个最大数组的长度,为Integer.Max_Value-8,
	*-8是为了使用Jvm所做出的保险操作,降低出错的概率*/

/*相关联的方法-hugeCapacity*/
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

至此,ArrayList的扩容机制我们已经理解到源码级了,现在可以小结一下:

ArrayList的扩容单位为旧容量的两倍,即新加入元素越容量的时候,容量翻一倍

copyOf

还差最后一个点:记得上面我没做出解释的这段代码么?

  //Arrays.copyOf?
	elementData = Arrays.copyOf(elementData, newCapacity);

这里可以看出来,是将旧的缓存数组进行复制,并且指定了新的容量,返回了一个新的缓存数组

没有看到任何针对ArrayList的操作,那现在明白了

elementData其实就是我们操作ArrrayList对象的真正结构,也就是说ArrayList不就是

一个数组么!!只是在ArrrayList这个类中,把它封装成了功能如此强大的结构!!

发布了184 篇原创文章 · 获赞 130 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/JunSIrhl/article/details/105697898