Java中的ArrayList

文章详细介绍了Java中ArrayList的数据结构,包括其通过数组实现、默认初始化、添加元素时的扩容机制,以及get、set和remove等基本操作的工作原理。重点讨论了ArrayList在添加元素时如何动态调整容量以适应元素增长的情况。

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

1.概述

众所周知,Java中的ArrayList是实现了List的接口,容器内地元素是有序、可重复、有索引因为底层通过数组的方式实现,并且是允许存入null值的。每一个ArrayList底层都是基于Object数组实现,每个ArrayList都有自己的capacity,当容器内的元素多于自己的capacity时,底层数组就会发生扩容。

2.ArrayList底层数据结构

一般我们在创建一个ArrayList对象的时候,默认的无参构造器在底层会做以下的处理:

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

代码当中的 this.elementData其实是一个Object[]数组,而

DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个不可变的空数组{}

因此ArrayList底层是通过数组的形式实现的

3.添加元素add

当我们创建好对象开始往ArrayList集合中添加元素的时候,底层会做如下的判断

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

这里的参数e是我们添加的元素, elementData是底层的Object[]数组,s是当前数组的长度,这段代码的逻辑是 当s等于底层数组的长度的时候,就会执行grow方法,并将grow方法返回的数组赋值给底层的Object[]数组,我们进入grow方法查看

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);
        } else {
            return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
        }
    }

首先这个minCapacity是当前的数组长度+1也就是add方法参数s的值+1,该段代码的逻辑是,先获取添加元素前数组的长度,然后判断当前数组的长度是否大于0或者当前数组的长度不等于底层维护的空数组,很显然,当我们第一次添加元素的时候,底层数组的长度肯定是0并且他是等于空数组的因为在new对象的时候已经赋值过了,所以直接进入else下面,此时return一个长度是10的空数组,此时元素添加进Object[]数组当中,其余9个全部是null。继续观察这段代码,当添加第二个元素的时候,在回到add方法中,此时s的值2,底层数组的长度是10,2 != 10因此不需要走grow方法,直接将数组索引为1赋值,当我们添加第11个元素的时候,此时底层s的值等于数组的长度我们在此来到grow方法,再次分析源码发现if里面的条件成立直接进入里面的方法执行,我们点进去查看源码

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

参数1是grow方法中添加元素之前的数组的长度也就是10,参数2是s的值11-Object[]数组的长度10也就是1,参数3是oldLength右移1位也就是10除以2等于5,所以按照newLength方法的判断,preLength的值是10 + 5 = 15 很显然15是远远小于Integer的最大值 - 8的所以此时会发生扩容,将底层数据的长度由10扩容到15,依次类推,当我们添加第16个元素的时候底层数组会扩容到22,添加的元素超过10之后,会以当前数组长度的0.5倍扩容。

4.get()

再次来到ArrayList中的get方法,此方法需要传入一个index也就是底层Object数组的索引源码如下:此方法过于简单我们不展开描述

public E get(int index) {
        // 首先判断index和底层数组的长度,如果index比size大则会抛出异常
        Objects.checkIndex(index, size);
        return elementData(index);
    }

5.set(int index, E element)

set方法是将索引为index处的元素更换为我们传递的element,然后返回旧值

public E set(int index, E element) {
        // 依旧是检查index是否比size大
        Objects.checkIndex(index, size);
        // 先将旧值取出
        E oldValue = elementData(index);
        // 将此索引处的值更换为新值
        elementData[index] = element;
        // 返回旧值
        return oldValue;
    }

6.remove(int index)

此方法用于将index索引处的值移除掉,并返回旧值源码如下

public E remove(int index) {
        Objects.checkIndex(index, size);
        // 创建一个局部变量,目的是提高执行效率,局部变量访问的速度比实例变量或静态变量快得多,因为局部变量保存在栈上而实例变量或者静态变量保存在堆或方法区中
        final Object[] es = elementData;

        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);

        return oldValue;
    }

private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            // 底层采用数组的浅拷贝
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

以上就是ArrayList常用的几个方法,谢谢观看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值