ArrayList底层学习02 -- 初始化及核心方法解析(add,get,grow)

本文深入解析ArrayList的源码,包括初始容量、最大容量设定,以及构造函数和add、remove方法的工作原理。重点阐述了扩容策略,如1.5倍扩容和防止内存溢出的处理。此外,还探讨了删除元素时如何优化性能。

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

一、属性

private static final int DEFAULT_CAPACITY = 10; 初始容量10
private int 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
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //默认的最大容量

ps:MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8,是因为有些虚拟机会预留数组的头部信息,一般数组头部是8位,所以减8;是为了避免一些虚拟机内存溢出的问题;
看源码上面的注解大概也是这个意思;有不对的欢迎指出!

二、构造函数
1、有参构造函数
a、设置初始容量

 public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
       //初始化数组的容量,定义后内存就会开辟出容量大小的内存空间
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
    	//初始化容量大小为0,只是把当前数组设置为空对象;相当于直接new ArrayList(),不指定初始大小
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

b、设置集合

public ArrayList(Collection<? extends E> c) {
     elementData = c.toArray();
     //入参是不为空的集合
     if ((size = elementData.length) != 0) {
         //如果入参c.toArray()不是Object[]类型
         if (elementData.getClass() != Object[].class)     
            //将入参集合以object类型复制到当前数组中
             elementData = Arrays.copyOf(elementData, size, Object[].class);
     } else {
         this.elementData = EMPTY_ELEMENTDATA;
     }
 }
解释:elementData.getClass() != Object[].class:因为toArray方法会被子类重写,那么返回的类型就可能不是Object[]类型,就会使该等式成立
原因:将入参集合转为Object对象,是为了可以避免父类转子类的问题出现,可以参考下面的列子

父类转子类的列子:

//解释new ArrayList(Collection<? extends E> c)中elementData.getClass() != Object[].class)的实现原理
ArrayList objectArray = new ArrayList(Arrays.asList("a","b"));
//Arrays.asList("a","b").toArray().getClass() 就不是object类型
System.out.println(Arrays.asList("a","b").toArray().getClass());
objectArray.add("a");
objectArray.add(new Object());
System.out.println(objectArray.toArray().getClass());

举例打印的结果
如果arrayList内部没有将存储元素转为Object类型的逻辑,那么objectArray.add(new Object()) 这行代码就会报错;因为Object类型转为String类型相当于父类转子类,是不合理的;

2、无参构造函数

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

给elementData 复制为一个空数组

ps: size():获取的是实际的使用的大小
length:数组的容量大小

三、add方法

public boolean add(E e) {
     ensureCapacityInternal(size + 1);   //判断是否需要扩容 size + 1:加了元素后的长度
     elementData[size++] = e;            //在数组末尾(下标size)加上新元素后,原size+1
     return true;
 }

看一下容量大小校验的方法:ensureCapacityInternal

private void ensureCapacityInternal(int minCapacity) {
  ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity:  这个方法主要是判断当前集合如果是空集合(new ArrayList()),在默认容量10和当前新增元素后容量(minCapacity)之前取最大值返回;不是空集合则返回minCapacity;

ensureExplicitCapacity:

private void ensureExplicitCapacity(int minCapacity) {
  modCount++;     //modCount当前集合修改次数加1
    if (minCapacity - elementData.length > 0)      //@1
        grow(minCapacity);   //扩容方法
}

@1: 如果新增元素后的实际长度minCapacity 大于当前数据的实际长度,则进行扩容
注意: 如果是通过new ArrayList()初始化数组,那么第一次添加元素时,取得的minCapacity就是10且实际数组长度为0,满足minCapacity > elementData.length需要进行扩容

grow:

 private void grow(int minCapacity) {
     int oldCapacity = elementData.length;
     int newCapacity = oldCapacity + (oldCapacity >> 1);    //按1.5倍扩容
     //如果扩容后的容量小于minCapacity,则新容量为minCapacity
     if (newCapacity - minCapacity < 0)
     //当new ArrayList()创建对象时就会出现这种情况,上述有详细说明
         newCapacity = minCapacity;   
     //如果新容量大于最大容量
     if (newCapacity - MAX_ARRAY_SIZE > 0)
     	//判断minCapacity是否小于0,小于则报错;minCapacity大于MAX_ARRAY_SIZE则取Integer.MAX_VALUE,否则取MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)
         newCapacity = hugeCapacity(minCapacity);
     elementData = Arrays.copyOf(elementData, newCapacity);  //copyOf这个方法很耗性能
 }

四、remove方法

 public E remove(int index) {
   //校验下标是否在实际长度size以内
   rangeCheck(index);
	//修改记录数加一
    modCount++;
    //获取该下标的值
    E oldValue = elementData(index);
    //获取需要移动的次数
    int numMoved = size - index - 1;
    //如果删除的不是最后一个下标,则都会触发这个条件
    if (numMoved > 0)
        //将需要删除的下标,后面的所有元素依次往前挪一个位置
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
         //将最后一个下标的元素设置为null,且实际长度size减一
        elementData[--size] = null; //由gc回收
    return oldValue;
}

ps:删除元素时,采用设置null的方法,可以让gc来回收,提高性能;

remove(Objetc o):核心方法fastRemove(index)和上面一致,也是通过移位和设置null来实现的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值