JAVA-基础知识-ArrayList扩容

本文详细介绍了JAVA ArrayList的扩容机制,初始容量为10,满时以1.5倍或预期长度扩容。当达到MAX_ARRAY_SIZE或Integer.MAX_VALUE时,超出部分元素会被舍弃。ArrayList适合单线程环境,因其线程不安全,多线程操作可能导致非原子性问题。文章还探讨了ArrayList的重要变量和扩容方法grow()的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JAVA-基础知识-ArrayList扩容

大概介绍:
ArrayList的扩容机制,在添加第一个元素时创建一个长度为10的数组(懒加载的方式),当前数组已满时会创建一个1.5倍原长度(如果小于预期长度则使用预期长度)的新数组,再将原有元素拷贝到新数组,当数组长度达到上限时则会以MAX_ARRAY_SIZE 或者 Integer.MAX_VALUE作为最大长度,而多余的元素就会被舍弃掉。

ArrayList是一个常用类,由数组实现,查询可在常数时间内实现,虽然在非结尾增删上效率不如LinkedList,但查询效率高,相对于vector,ArrayList是线程不安全的(可以通过Collections.synchronizedList()实现线程安全),所以更适用单线程环境。

线程不安全的原因:
对ArrayList的操作一般分为两个步骤,改变位置(size)和操作元素(e)。所以这个过程在多线程的环境下是不能保证具有原子性的,因此ArrayList在多线程的环境下是线程不安全的。

由于使用数组实现,需要对数组长度进行定义。

首先认识ArrayList的几个重要变量:

/*序列化ID*/
    private static final long serialVersionUID = 8683452581122892189L;
    /*默认的数组容量,第一次进行添加操作时,如果ArrayList为空或者添加元素后的数组长度小于等于10,会将该值作为数组的初始长度*/
    private static final int DEFAULT_CAPACITY = 10;
    /*一个空的对象数组,在需要将elementData数组置空时,会将该对象赋予elementData*/
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /*默认的空对象数组,在ArrayList构造时,默认将该对象给elementData*/
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /*ArrayList内部用于存储元素的数组,transient关键字修饰,不使用默认序列化*/
    transient Object[] elementData; // non-private to simplify nested class access
    /*数组的长度,是ArrayList中实际元素的个数,不是数组的容量*/
    private int size;

在ArrayList中,每一次的插入操作中都使用到了ensureCapacityInternal()方法确保数组内部容量,在使用该方法进行容量判断之后,才会将增加的元素添加到数组中,下面以尾部插入为例;

/**
     * 增加数据元素到集合得末尾
     * 
     */
    public boolean add(E e) {
        /*判断是否扩容,如果原来的元素个数是size,那么增加一个元素之后的元素个数为size + 1,所以需要的最小容量就为size + 1*/
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal()将判断委托给ensureExplicitCapacity()处理

/*获取数组最小容量*/
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        /*如果elementData为空,且minCapacity <= 10,都会以DEFAULT_CAPACITY作为最小容量*/
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureCapacityInternal(int minCapacity) {
        /*ensureCapacityInternal方法委托给ensureExplicitCapacity方法*/
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        /*如果minCapacity大于elementData的长度,使用grow方法进行扩容*/
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

下面就是扩容的实现方法grow方法:

/*扩容方法*/
    private void grow(int minCapacity) {
        /*原有数组容量*/
        int oldCapacity = elementData.length;
        /*新的数组容量,下面位运算相当于newCapacity = oldCapacity * 1.5 向下取整*/
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        /*如果新的数组容量小于需要的最小容量,即假设新的数组容量是15,最小需要16的容量,则会将16赋予newCapacity*/
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        /*变量MAX_ARRAY_SIZE = 2147483639 [0x7ffffff7],如果扩容后的新容量大于这个值则会使用hugeCapacity方法
         * 判断最小容量minCapacity是否大于MAX_ARRAY_SIZE,如果需要最小容量的也大于MAX_ARRAY_SIZE,则会以
         * Integer.MAX_VALUE = 2147483647 [0x7fffffff]的值最为数组的最大容量,如果没有则会以MAX_ARRAY_SIZE最为最大容量
         * MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,为什么用MAX_ARRAY_SIZE ,源码的中的说法是一些虚拟机中会对数组保留一些标题字段
         * 使用Integer.MAX_VALUE会造成内存溢出错误
         * */
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        /*确定数组最终的容量newCapacity之后,将原有ArrayList的元素全部拷贝到一个新的ArrayList中*/
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
        /*如果minCapacity小于0,则抛出内存溢出错误*/
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; 
    }

如果数组长度达到上限,则会以
MAX_ARRAY_SIZE 或者 Integer.MAX_VALUE作为最大长度,而多余的元素就会被舍弃掉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值