源码剖析——ArrayList动态扩容机制

本文深入解析ArrayList的实现原理,包括其继承关系、实现的接口以及内部动态扩容机制。ArrayList通过实现RandomAccess接口提供快速的随机访问,通过Cloneable接口支持对象拷贝。在添加元素时,ArrayList会根据需要动态扩容,初始容量为10,当容量不足时,使用1.5倍的旧容量进行扩容。此外,文章还展示了如何通过反射获取ArrayList的容量。
摘要由CSDN通过智能技术生成

正文:
ArrayList继承了AbstractList类,实现了List接口,并且ArrayList底层是一个动态扩容的数组。ArrayList实现了RandomAccess接口,此接口是一个随机访问的标记接口(不需要遍历,直接通过下标访问数组元素的内存地址),此外还实现了Serializable接口支持序列化(就是将对象转化为字符序的形式,这些字符序列包括了对象的字段和方法,序列化的对象可以被写入数据库,文件,也可以用于网络传输),同时ArrayList还支持拷贝实现了Cloneable接口


图解展示ArrayList的实现和继承关系:

在这里插入图片描述
ArrayList源码 :

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

ArrayList类实现了RandomAccess接口,该接口是一个随机访问的标记接口

//没有任何的方法
public interface RandomAccess {
}

如何通过下标直接访问内存地址呢,其实是做了下面这件事
这也就是为什么ArrayList查询快的原因

 E elementData(int index) {
        return (E) elementData[index];
 }
 public E get(int index) {
     	//这个方法是检查下标是否越界
        rangeCheck(index);
        return elementData(index);
 }
 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
 }

ArrayLarrist类实现了Cloneable接口,说明该类支持拷贝,ArrayLarrist的内部确实重写了Object 的clone() 方法。

public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
}

ArrayList动态扩容机制
ArrayList提供了三种初始化方法

 public ArrayList() 
 public ArrayList(Collection<? extends E> c)
 public ArrayList(int initialCapacity)

无参构造方式

     /**
     * Constructs an empty list with an initial capacity of ten.
     */
  public ArrayList() {
   		//elementData指向了一个空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
   }

源码上面的注释是说初始化了一个容量为10的数组,但是elementData指向了一个空数组
无参构造还是不能说明问题继续往下看

     //默认初始化容量为10
    private static final int DEFAULT_CAPACITY = 10;
    
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    //transient关键字作用:该字段不被序列化
    transient Object[] elementData; 
    
    private int size;

有参构造方式(1)

 public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[elementData ];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
}

initialCapacity形参表示一个初始化的容量可以理解为最初创建对象的时候给的一个容量
如果这个initialCapacity大于0,elementData 初始化的容量就是new Object[elementData ]
如果这个initialCapacity等于0,elementData就指向了一个空数组。
如果这个initialCapacity小于0, 抛出异常

这段代码就会抛出以下异常

java.lang.IllegalArgumentException: Illegal Capacity: -10

 List<Integer> list = new ArrayList<Integer>(-10);
        try {
            for (int i = 0; i < 10; i++) {
                list.add(i);
            }
            for (Integer element : list) {
                System.out.println(element);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

有参构造方式(2)

public ArrayList(Collection<? extends E> c) {
        elementDatca = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
}

这个有参构造是将一个集合作为ArrayList的元素 elementDatca就是c转化的数组
Arrays.copyOf(elementData, size, Object[].class) 复制数组内容给ArrayList类中字段elementData
赋值。

通过构造方法的了解还是不能够知道如何的动态扩容,那么重点来了

假设现在添加第十一个元素,下标(索引)是10,也就是size是10执行ensureCapacityInternal(size + 1)

add()方法

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
 }

ensureCapacityInternal()方法是一个确保容量的方法,ensureCapacityInternal方法中calculateCapacity(elementData, minCapacity) 判断elementData是否为空数组,如果是就使用Math.max(DEFAULT_CAPACITY, minCapacity);比较出来一个大的,此时应该返回的是minCapacity
minCapacity的值是11,执行方法ensureExplicitCapacity,判断minCapacity是否超过当前集合的容量elementData.length,如果超过就执行grow(minCapacity),现在minCapacity是11,所以继续执行grow方法


   private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
  private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
  }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

执行grow方法 将数组的长度(这个长度是默认的10)赋值给了oldCapacity
执行语句 int newCapacity = oldCapacity + (oldCapacity >> 1);这里涉及到了右移运算符
右移运算符:右侧移除部分舍弃,左侧补符号位(0表示整数1表示复数) 表示: 10>>1
0000 1010 —> 0000 0101 右移一位以后是十进制5
所以是扩大到原来的1.5倍,继续判断如果新容量没有超过当前的minCapacity
就将新的容量指向minCapacity,如果新容量超过最大容量就执行 hugeCapacity(minCapacity);
以上判断都不满足就执行Arrays.copyOf(elementData, newCapacity);将原有的数组复制到新分配的内存地址上

//默认的容量 如果超过这个容量就扩容
private static final int DEFAULT_CAPACITY = 10;
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

通过反射获取容量的大小

  public static Integer getCapacity(ArrayList list) {
        Integer length = null;
        Class clazz = list.getClass();
        Field field;
        try {
            field = clazz.getDeclaredField("elementData");
            field.setAccessible(true);
            Object[] object = (Object[]) field.get(list);
            length = object.length;
            return length;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return length;
    }

验证数组动态扩容的过程:

  • 不超过默认容量
public class ArrayListTest {
    public static void main(String[] args){

         ArrayList<Integer> list = new ArrayList<Integer>();
        for (int i = 1; i < 5; i++) {
            list.add(i);
        }
        Integer capacity = getCapacity(list);
        System.out.println("集合容量:"+capacity);
        int size = list.size();
        System.out.println("集合大小:"+size);
		//集合容量:10
        //集合大小:4
    }
}
  • 超过默认容量
        ArrayList<Integer> list = new ArrayList<Integer>();
        List<Integer> newList = new ArrayList<Integer>();
        for (int i = 1; i < 15; i++) {
            list.add(i);
        }
        Integer capacity = getCapacity(list);
        System.out.println("集合容量:"+capacity);
        int size = list.size();
        System.out.println("集合大小:"+size);
		//集合容量:15
        //集合大小:14
  • 如果先增加5个,然后在增加15个,集合容量新容量等于最小容量
 		ArrayList<Integer> list = new ArrayList<Integer>();
        ArrayList<Integer> newList = new ArrayList<Integer>();
        for (int i = 1; i <= 5; i++) {
            list.add(i);
        }
        for (int i = 0; i < 15; i++) {
            newList.add(i);
        }
        list.addAll(newList);
		//集合容量:20
		//集合大小:20
  • 如果添加20元素呢,根据前面的理解 当添加了第11个元素的时候,通过oldCapacity + (oldCapacity >> 1)容量扩容到了15,当添加到16个元素的时候继续扩容此时新容量是22,所以集合容量是22,集合大小是20
        ArrayList<Integer> list = new ArrayList<Integer>();
        for (int i = 1; i <= 20; i++) {
            list.add(i);
        }
        //集合容量:22
       //集合大小:20

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值