Java 集合框架学习 - List:ArrayList 的扩容机制

2 篇文章 0 订阅
1 篇文章 0 订阅

 Java集合框架是十分重要且具有学习意义的,今天对它的List接口下的ArrayList实现类的扩容机制进行深入学习,这篇文章也就应运而生。

目录

开始

ArraysSupport.newLength 方法

常量值 SOFT_MAX_ARRAY_LENGTH 是什么

一次性添加多个元素超出 ArrayList 原容量

System.arraycopy 方法


开始

ArrayList底层其实就是一个简单的用于存储Object类型的数组,如果不加以设置,那么初始化长度是0

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

其中 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是长度为0的空数组。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

那么ArrayList是什么时候初始化容量的呢?由ArrayList的源码文档可知,当添加第一个元素时,任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展到 DEFAULT_CAPACITY

 而这个 DEFAULT_CAPACITY  是什么呢?下面由一个小 demo ,带着我们一起去了解一下。

public class test01 {
    public static void main(String[] args) {
        outOfIndexWithAddOne();

//
    }

    private static void outOfIndexWithAddOne() {
        ArrayList<String> list = new ArrayList<>();
        list.add("你好");
        String[] strs = {"a","a","a","a","a","a","a","a","a"};
        list.addAll(Arrays.asList(strs));
        // -----------------------------------
        list.add("你好啊"); //TODO expanding the previous capacity.
        System.out.println(list);
    }

}

当调用 add() 方法向 ArrayList 中添加进第一个元素,数组的扩容机制就开始工作了。

    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

//

    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

//

    private Object[] grow() {
        return grow(size + 1);
    }

grow() 方法就是扩容机制的重点

    private Object[] grow(int minCapacity) {
        /*
            添加第一个元素时,oldCapacity 的值是0,if条件不成立
            执行 new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
            此时 minCapacity 的值为 1
    
        */
        int oldCapacity = elementData.length;
        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth */
                    oldCapacity >> 1           /* preferred growth */);
            return elementData = Arrays.copyOf(elementData, newCapacity);
        } else {
            return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
        }
    }
    private static final int DEFAULT_CAPACITY = 10;

因此,往 ArrayList 容器中添加第一个元素时,容器容量被初始化为10

如果接着往容器中存入元素使之超出初始容量10,容器会执行扩容机制,也就是if条件走的分支。

ArraysSupport.newLength 方法

我们先简单地介绍一下工具类 ArraysSupport 提供的 newLength 方法

    public static int newLength(int oldLength, int minGrowth, int prefGrowth) {}

这样一个公共的静态方法需要三个参数值,分别是:

  • oldLength – current length of the array (must be nonnegative) :当前容器容量,必须是非负值
  • minGrowth – minimum required growth amount (must be positive) :最小扩容量,同样必须是正数
  • prefGrowth – preferred growth amount :首选增值(扩容量)

接下来一起看一下它的具体工作流程

        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth */
                    oldCapacity >> 1           /* preferred growth */);
            return elementData = Arrays.copyOf(elementData, newCapacity);
        }


        public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
            // preconditions not checked because of inlining
            // assert oldLength >= 0
            // assert minGrowth > 0

            int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
            if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
                return prefLength;
            } else {
                // put code cold in a separate method
                return hugeLength(oldLength, minGrowth);
            }
        }


扩容后的容量就是在老容量基础上加上合适的扩容长度。需要注意的是

int prefLength = oldLength + Math.max(minGrowth, prefGrowth);

可能会造成溢出。此时需要走 hugeLength() 方法

    private static int hugeLength(int oldLength, int minGrowth) {
        int minLength = oldLength + minGrowth;
        if (minLength < 0) { // overflow
            throw new OutOfMemoryError(
                "Required array length " + oldLength + " + " + minGrowth + " is too large");
        } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
            return SOFT_MAX_ARRAY_LENGTH;
        } else {
            return minLength;
        }
    }

这个方法里面,如果扩容后确实超过了 int 整形所能存储的最大值,就会抛出 OutOfMemoryError 异常。否则,就与 SOFT_MAX_ARRAY_LENGTH 的值进行比较,从而返回最合适的结果进行扩容。

常量值 SOFT_MAX_ARRAY_LENGTH 是什么

这里我们再来说说这个SOFT_MAX_ARRAY_LENGTH

它是数组增长计算施加的软最大数组长度某些 JVM(如 HotSpot)具有实现限制,如果请求在 Integer.MAX_VALUE 附近分配某个长度的数组,即使有足够的堆可用,也会导致抛出 OutOfMemoryError(“请求的数组大小超过 VM 限制”)。实际限制可能取决于某些特定于 JVM 实现的特征,例如对象标头大小。保守地选择软最大值,以便小于可能遇到的任何实现限制。

    public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;

以上就是当 ArrayList 的初始化容量和扩容机制。但是如果在 ArrayList 容量无法支持大批元素的一次性加入,扩容机制又该如何实现?下面我们来研究一下

一次性添加多个元素超出 ArrayList 原容量

当调用 addAll() 方法一次性批量地填充元素到 array 中,此时 array 容量不足以支持加入,就需要另外一种扩容机制,而不是在原来的基础上慢慢扩充 array 的容量。

先看 demo

public class test01 {
    public static void main(String[] args) {
        outOfIndexWithAddAll();
//
    }

    private static void outOfIndexWithAddAll() {
        ArrayList<String> list = new ArrayList<>();
        list.add("你好");
        String[] strs = {"a","a","a","a","a","a","a","a","a"};
        list.addAll(Arrays.asList(strs));
        // -----------------------------------
        String[] strs2 = {"b","b","b","b","b","b","b","b","b"}; //TODO expanding the previous capacity.
        list.addAll(Arrays.asList(strs2));
        System.out.println(list);
    }
}

扩容机制在 addAll() 中执行

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        modCount++;
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))
            elementData = grow(s + numNew);
        System.arraycopy(a, 0, elementData, s, numNew);
        size = s + numNew;
        return true;
    }

如果加入的元素数量超出了原容器的容量,那么就调用 grow() 方法执行扩容,此时扩容的话是根据加入元素的数量大小进行扩容的:

  1.  如果原容器还没被填充满,就只扩容需要的容量;
  2.  如果原容器已经满了,就扩容添加的元素的数量

如图

最后再对数据进行拷贝到扩容后的容器中。

System.arraycopy 方法

这里再补充讲一下 System.arraycopy 方法

这一个方法是一个被 native 修饰的方法,用途就是数组或者集合间的拷贝。

    @IntrinsicCandidate
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

它需要 5 个参数值:

  • Object src:源数组
  • int srcPos:源数组中的起始位置
  • Object dest:目标数组
  • int destPos:目标数组中的起始位置
  • int length:要复制的数组元素长度

注意:这里的起始位置指的都是拷贝的起始位置 。int length 指的是要从原数组中拷贝到目标数组的元素个数

测试

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值