《一步一步看源码:ArrayList》容器源码系列之一

本文详细解析了Java ArrayList的源码,包括其内部结构、添加元素(扩容机制)、获取元素以及删除元素的过程。通过对ensureCapacityInternal、ensureExplicitCapacity和grow方法的探讨,展示了ArrayList如何动态调整容量以适应元素的增删。同时,文章还介绍了remove方法中如何通过System.arraycopy实现元素移动以及垃圾回收机制。
摘要由CSDN通过智能技术生成

题容器源码

温馨提醒:idea的进入源码只要点击鼠标滑轮,返回上级只要输入Ctrl+alt+←

ArrayList:

  • 底层数据结构:数组,没什么好说的,后续比如链表或者红黑树,我会细讲。
   public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//(值为空{},空数组)
    } 
Add方法:(面试可以秀一下grow过程)
  • 第一次新增元素,minCapacity=size+1=0+1=1(size初始为0)
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // size初始为0
        elementData[size++] = e;
        return true;
    }
  • 点击ensureCapacityInternal再往底层
    • (elementData、DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个对象数组,初始为空)
    • DEFAULT_CAPACITY初始为10,可以说是初始容量,(但不是在这里决定的
 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //如果这是第二个元素放入,则不会进入判断而是直接返回minCapacity大小的容量
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);  //取出最大值,此时minCapacity=10
        }
        ensureExplicitCapacity(minCapacity);
    }
  • 在进入ensureExplicitCapacity方法里查看
    • modCount初始为0,并且被transient修饰,保证不参与序列化和反序列化。是一个计数器,记录ArrayList进行add和remove的次数。
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;   						//记录add和remove操作次数
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)   //elementData初始为0代表原有数组,minCapacity初始为10 
            grow(minCapacity);
    }
  • ArrayList扩容
    • oldCapacity代表原来数组长度:旧的数组长度
    • newCapacity代表新生成的数组长度:旧的数组长度+旧的数组长度右移一位
    • MAX_ARRAY_SIZE:Integer的最大长度-8(减去8是因为jvm运行需要一些头字,大小为8)
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;  //拿到原来数组
    int newCapacity = oldCapacity + (oldCapacity >> 1); //>>代表右移,右移移位相当于除2,所以整体长度为1.5倍原数组
    if (newCapacity - minCapacity < 0) //如果新的数组长度小于minCapacity长度(初始为size+1,为1,后面变为10)
        newCapacity = minCapacity;  //扩容长度变为初始容量10
    if (newCapacity - MAX_ARRAY_SIZE > 0) //怕超过数字的最大容量-8,为什么减去8,因为jvm会保存一些头字,这些为8
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
  • 进入hugeCapacity源码继续查看
	private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow  //为什么超过了Integer最大长度-8还会小于0,因为溢出了二进制最大值
            throw new OutOfMemoryError(); //OOM异常
        return (minCapacity > MAX_ARRAY_SIZE) ?	Integer.MAX_VALUE :MAX_ARRAY_SIZE;
        //如果没有溢出,则妥协 返回Integer最大长度给她
    }
  • 最后使用copyOf,将旧数组的值赋值给新数组
 elementData = Arrays.copyOf(elementData, newCapacity);
  • 最后再返回add方法里,将索引挪到下一位,然后把元素添加进去
  elementData[size++] = e;
        return true;
Get方法:
  • get方法进入
   public E get(int index) {
        rangeCheck(index);  //进入判断索引有没有超过数组最大长度
        return elementData(index);
    }

  • 进入rangeCheck方法
    private void rangeCheck(int index) {
        if (index >= size)  //索引不能超过数组最大值,否则异常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
  • 进入elementData方法
    • 这里面的E就是T,泛型
    E elementData(int index) {
        return (E) elementData[index];  //从elementData数组中取值返回
    }
Remove方法:(面试可以秀一下arraycopy过程)
  • remove方法是通过元素移动来实现的,先提取出要移动的元素,然后进行移动,最后将其值赋为空,jvm会帮我们自动处理数组内值为null的元素。
public E remove(int index) {       //默认数组为{a1,a2,a3,a4,a5}
    rangeCheck(index); 
    modCount++; //计数器,记录add和remove了几次操作
    E oldValue = elementData(index); //从数组中取出索引对应元素
    int numMoved = size - index - 1; //记录要移动的元素个数,例如你删除的元素索引值为“2”,则得到2
    if (numMoved > 0)				//当要移动的值大于0,因为有可能删除的是最后一个元素,就没有移动的元素
        System.arraycopy(elementData, index+1, elementData, index,numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}
  • rangeCheck:检查索引是否超过数组范围
   private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
  • System.arraycopy:是一个本地方法,下面方法运行过程
    • 默认数组为{a1,a2,a3,a4,a5},删除了a3,此时numMoved=2,要移动2个元素。
    • 第一个传入数组是原数组,从index+1(a4)开始复制numMoved位(2位)带第二个elementData的第三个位置中(原本a3位置)
    • 最终得到的数组是{a1,a2,a4,a5,a5}
  public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);
 //下面是从remove里抠下来的方便解释
//
  System.arraycopy(elementData, index+1, elementData, index,numMoved);
  • 最终将最后一个元素赋值为null,让JVM的GC帮我们处理空值,结果为{a1,a2,a4,a5}
  elementData[--size] = null; // clear to let GC do its work
    return oldValue;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值