源码分析_ArrayList,关键扩容分析

ArrayList是Java中的动态数组,其容量可随元素增加而自动扩展。与线程安全的Vector不同,ArrayList操作不是线程安全的,适合单线程使用。在添加元素时,若容量不足,会进行扩容,扩容策略通常是原容量的1.5倍,但最大容量受Integer.MAX_VALUE限制。此外,ArrayList提供了如add、contains、get、remove等常用方法。

ArrayList简介

ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。

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

ArrayList属性

ArrayList属性主要就是当前数组长度size,以及存放数组的对象elementData数组,除此之外还有一个经常用到的属性就是从AbstractList继承过来的modCount属性,代表ArrayList集合的修改次数。

private static final int DEFAULT_CAPACITY = 10;     //容量

private static final Object[] EMPTY_ELEMENTDATA = {}; // 一个空对象

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 一个空对象,默认构造函数创建

transient Object[] elementData;  //数组

private int size; //长度

ArrayList构造函数

无参构造

public ArrayList() {
		this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //空对象
	}

注意:此时我们创建的ArrayList对象中的elementData中的长度是1,size是0,当进行第一次add的时候,elementData将会变成默认的长度:10.

含参构造

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];  //new一个新数组
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;            //空对象
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+  //非法参数
                                           initialCapacity);
    }
}

插入数据:add()方法

注意size时元素个数,elementData.length是当前数组容量

    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;
    }

在末尾添加元素:首先要判断是否需要扩容,传入s为当前元素个数size,若s == elementData.length则需要调用grow()扩容

    public void add(int index, E element) {
        rangeCheckForAdd(index);   //检查边界
        modCount++;					//修改次数加一
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();  //当前元素个数size == 当前数组容量length 时,grow扩容
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);    //index后的元素后移
        elementData[index] = element;   //插入
        size = s + 1;
    }

在指定位置添加元素:首先rangeCheckForAdd(index)判断索引是否超出数组下标,之后检测是否需要扩容**(当前元素个数size == 当前数组容量length 时,grow扩容)**

System.arraycopy()把指定位置的以后的元素向后移一位,将index位置赋新值

补充System.arraycopy()方法:

将指定源数组(前)中的数组从指定位置复制到目标数组(后)的指定位置。

扩容方法grow()

private Object[] grow() {
     return grow(size + 1);  //扩容需要的最小容量为size+1
 }

private Object[] grow(int minCapacity) {
    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); 
        //复制旧数组,赋值给新数组,长度为newCapcity(不足的长度用0填充)
    } else { //初始化的扩容
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

扩容步骤:

使用Arrays.copyOf(elementData, newCapacity), 复制旧数组到新数组,长度为newCapcity(不足的长度用0填充)

minCapacity - oldCapacity是最小需要增长的容量,

oldCapacity >> 1这是期待增长的容量,原容量的0.5倍

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

(设置一个阈值,保留8的长度,防止一些异常)

源码中定义MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;上面的注释也写明白了。

一些vm可能会在数组中保留一些header信息,分配更大的长度可能会导致OutOfMemoryError异常。
这里这样做的原因是为了尽可能的避免因为vm使用了数据保存header的信息而导致分配更大的长度产生OutOfMemoryError异常。但是并不一定超出这个长度一定会异常。这只是为了尽可能的去避免。但是假使当一个vm使用了数组保存一些header,并且这些header使用的长度大于8时那么当数组扩容到2^31-1再减去header的信息长度时依旧会发生OutOfMemoryError异常。

所以Arraylist的最大长度为2147483647即2^31-1

决定扩容大小的函数:

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) { //大于0小于阈值
        return prefLength;    //范围符合要求,增长1.5倍
    } else {
        // put code cold in a separate method
        return hugeLength(oldLength, minGrowth);  //prefLength大于阈值时
    }
}

private static int hugeLength(int oldLength, int minGrowth) {
    int minLength = oldLength + minGrowth;
    if (minLength < 0) { // overflow,溢出了,最大长度2^31-1
        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;     //否则分配minLength最小增长大小
    }
}

综上,扩容大小:

  1. 调用add方法,数组容量不足,需要扩容。
    1)使用无参构造创建的ArrayList,并且是第一次添加元素,扩容后的新容量为10
    2)在添加元素时容量不足,扩容后的新容量为原先容量的1.5倍(除过初始容量为1的ArrayList第一次扩容,它第一次扩容后的容量为2)。
    3)原先容量的1.5倍prefLength大于阈值SOFT_MAX_ARRAY_LENGTH,并且最小扩容量minLength小于阈值,则新容量为阈值SOFT_MAX_ARRAY_LENGTH
    若最小扩容量也大于阈值,那新容量为最小扩容量minLength

ArrayList类常用方法

import java.util.ArrayList;

  • ArrayList() 构造一个初始容量为十的空列表。
Modifier and TypeMethod and Description
booleanadd(E e) 将指定的元素追加到此列表的末尾。
booleancontains(Object o) 如果此列表包含指定的元素,则返回 true
Eget(int index) 返回此列表中指定位置的元素。
intindexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
Eremove(int index) 删除该列表中指定位置的元素
Eset(int index, E element) 用指定的元素替换此列表中指定位置的元素。
voidsort(Comparator<? super E> c) 使用提供的 Comparator对此列表进行排序以比较元素。(默认为升序)
Comparator.reverseOrder() 参数降序排序
Comparator.naturalOrder() 参数升序排序
List subList(int fromIndex, int toIndex)返回此列表中指定的 fromIndex (包括)和 toIndex之间的独占视图。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值