ArrayList新增元素源码分析

ArrayList源码学习

在学习源码前,我们先来看看其中几个重要的成员变量

	//序列化id,用于在序列化和反序列化过程中进行核验的一个版本号
	private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     * 默认初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     * 用于空实例的共享空数组实例。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 也是一个空数组
     * 和上面的区别
     * 	当无参构造时,Obeject数组elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
     * 当有参构造时,如果给定初始容量为0,或者传入集合为空集合(不是null),那么,将空数组EMPTY_ELEMENTDATA赋给elementData;
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /** 
     * ArrayList的缓冲区
     * 
     * 注意:
     * 被transient修饰的变量默认不参与序列化和反序列化。
     * 当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     *ArrayList的大小
     * @serial
     */
    private int size;

ArrayList的构造函数

通过对ArrayList源码,我们可以明显看见ArrayList一共有3个构造方法

  1. 无参构造函数
public ArrayList() {
		//初始化一个空的数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  1. 类型为int的有参构造
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
        	//如果参数大于0,就初始化一个长度为initialCapacity的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        	//如果输入参数等于0,就初始化一个空的数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
        	//如果输入参数小于0,表示初始化不合法,直接抛异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
  1. 传入集合的构造函数
    public ArrayList(Collection<? extends E> c) {
    	//首先将传入的集合转换为数组
        Object[] a = c.toArray();
        //判断传入的集合是否是一个空集合
        if ((size = a.length) != 0) {
        	//不是空集合,判断是否是Object对象数组
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
            	//不是Object数组,进行一次拷贝转换为Object数组
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 如果是空集合,初始化一个空数组
            elementData = EMPTY_ELEMENTDATA;
        }
    }

ArrayList的add()方法

public boolean add(E e) {
		//新增时,将当前size+1作为参数调用ensureCapacityInternal方法,判断数组是否存的下
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        
        elementData[size++] = e;
        return true;
    }

新增时,首先调用ensureCapacityInternal方法,传入参数为当前集合大小+1(即 size+1)。size+1表示elementData需要的最小长度。

//判断当前集合能不能放下即将被添加的元素,如果不能,扩容
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

该方法只做了一件事,判断能不能放下即将被添加的元素,如果不能,扩容

private static int calculateCapacity(Object[] elementData, int minCapacity) {
		//如果集合还没有被初始化,则返回初始容量和所需容量的较大的一个。初始容量是10,可知当我们第一次调用add()方法时,calculateCapacity返回的是10。如果已经初始化过了,直接返回minCapacity。
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

该方法的作用是:获取了当前集合需要的最小长度

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

        //判断是否需要进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

ensureExplicitCapacity方法判断了当前集合是否需要进行扩容

 private void grow(int minCapacity) {
   // 当前容量赋值给oldCapacity 
   int oldCapacity = elementData.length;
      //新容量为老容量的1.5倍。oldCapacity >> 1相当于除以2
   int newCapacity = oldCapacity + (oldCapacity >> 1);
      //如果扩容后还不够,则容量为minCapacity
   if (newCapacity - minCapacity < 0)
     newCapacity = minCapacity;
      //如果新容量大于MAX_ARRAY_SIZE,调用hugeCapacity()方法
   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);
 }

grow方法是集合的扩容方法,可以看出扩容量是原数组的1.5倍。
在该方法中还调用了一个hugeCapacity方法。

 private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

hugeCapacity方法只在扩容时可能被调用,它的逻辑很简单,先做了个简单的判断,之后执行了一个三元表达式,如果扩容前所需最小容量大于数组最大长度,返回Integer的最大值,否则返回MAX_ARRAY_SIZE。MAX_ARRAY_SIZE是集合设定的默认最大扩容长度,MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8=2^31-8

总结

ArrayList的add方法做了什么事呢?第一次进行add()操作时,会判断是否初始化,如果没有初始化,会将默认的空数组扩容为长度为10的数组,然后将数据放入新数组中。
注意:《阿里巴巴Java开发手册》里面建议初始化集合时尽量显示的指定集合大小,原因:1、如果未指定集合大小,在add时会进行扩容操作,会降低性能。
2、节约内存。需要多长的数组就开多长,避免内存浪费

添加到指定位置 add(int index, E element)

public void add(int index, E element) {
		//判断index是否合法,即判断是否小于零,是否大于当前size
        rangeCheckForAdd(index);

		//和add()中的一样
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将index及其后面的元素向后移一位,空出index
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //插入元素
        elementData[index] = element;
        //当前长度+1
        size++;
    }
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

添加所有元素addAll(Collection<? extends E> c)

public boolean addAll(Collection<? extends E> c) {
		//将集合转为对象数组
        Object[] a = c.toArray();
        //获取数组长度
        int numNew = a.length;
        //确保对象数组elementData有足够的容量,可以将新加入的a对象数组加进去
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //将数组a拷贝到element中
        System.arraycopy(a, 0, elementData, size, numNew);
        //现在element长度为size+numNew
        size += numNew;
        //若加入的是空集合则返回false
        return numNew != 0;
    }

添加所有元素addAll(int index, Collection<? extends E> c)

public boolean addAll(int index, Collection<? extends E> c) {
		//判断index是否合法,即判断是否小于零,是否大于当前size
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
		
		//确定数组需要移动的长度
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

总结

1、ArrayList的底层是数组,初始容量是10,当数组满了之后,继续添加元素时,会扩容到原来的1.5倍+1。

2、ArrayList保存了一个modCount属性,修改集合的操作都会让其自增。如果在遍历的时候modCount被修改,则会抛出异常,产生fail-fast事件(即当我们在调用add()、remove()这些修改集合的方法时,都会修改一个属性modCount。而我们在遍历集合时,首先会保存一份modCount,然后在遍历时,将保存的modCount和成员变量modCount对比,如果不一样,说明被集合已经被修改,抛出ConcurrentModificationException,产生fail-fast事件)。

3、ArrayList内部还维护了一个size属性,它是用来记录数组中的实际元素个数。
size,modCount,elementData这些成员变量,都注定了ArrayList线程不安全。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
ArrayListJava中常用的动态数组实现,它可以根据需要动态地增加或缩小数组的大小。当ArrayList中的元素数量超过了初始容量ArrayList会自动扩容,以便能够存储更多的元素。下面我们来看一下ArrayList扩容的源码实现。 在ArrayList中,扩容是由ensureCapacity方法实现的。当元素数量超过了数组容量,该方法会调用grow方法来扩容数组。 ``` private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } 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); } ``` 首先,ensureCapacityInternal方法会调用ensureExplicitCapacity方法,该方法会检查是否需要扩容,并在需要调用grow方法。 grow方法会首先计算新的数组容量,它的计算方式是将原来的容量增加一半。然后,grow方法会检查新容量是否大于最大数组容量,如果是,则调用hugeCapacity方法来返回一个足够大的容量值。最后,grow方法会调用Arrays.copyOf方法来将原来的数组复制到新的数组中。 需要注意的是,在进行扩容操作ArrayList会创建一个新的数组,并将原来的元素复制到新的数组中。这个过程会占用一定的间和空间,因此,在使用ArrayList,应该尽量避免频繁地进行扩容操作,以提高性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深藏bulu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值