ArrayList源码学习——add(E e)方法

前言

本文主要是对ArrayList源码中add(E e)方法进行一个简单的学习,更好的了解ArrayList添加数据的过程


提示:本篇文章是在学习过程中的一个记录,还存在一些问题,希望各位的补充

一、add方法源码

以下代码截取了ArrayList中的add(E e)的方法的实现过程,构造方法只截取了传入初始容量和缺省构造方法两种。

package com.chengqi.javase.learningexamples.sourcecodeanalysis;

import java.util.Arrays;

/**
 * 对于ArrayList的源码中的add(E e)方法进行一个分析
 * 该示例代码是以一个Student类作为要添加的数据的类型
 *
 * 未实现泛型
 * 该ArrayList集合,调用无参构造方法时,会构造出一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA,容量为0
 * 添加一个元素之后,会扩容为10,每次扩容的大小为1.5倍
 */
public class ArrayList {

    /**
     * 默认的初始化容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 用于空实例的共享空数组实例,
     * 即构造一个指定初始容量为0的一个空数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     *用于默认大小的空实例的共享空数组实例,
     * 在调用无参构造方法,且未给该ArrayList赋值时,构造一个空数组实例
     * 我们将其与EMPTY_ELEMENTDATA分开来,以了解添加第一个元素时要膨胀(扩容)多少。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存储ArrayList元素的数组缓冲区。
     * ArrayList的容量是此数组缓冲区的长度。
     * 添加第一个元素时,任何elementData==DEFAULTCAPACITY_empty_elementData的空ArrayList
     * 都将扩展为DEFAULT_CAPACITY。
     */
    transient Object[] elementData;

    /**
     *ArrayList实际存储的大小(它包含的元素个数)
     */
    private int size;

    /**
     * 记录了结构性改变的次数。
     * 结构性改变指的是那些修改了列表大小的操作,在迭代过程中可能会造成错误的结果。
     * madCount交由迭代器(Iterator)和列表迭代器(ListIterator)使用,
     * 当进行next()、remove()、previous()、set()、add()等操作时,
     * 如果madCount的值意外改变,
     * 那么迭代器或者列表迭代器就会抛出ConcurrentModificationException异常。
     */
    protected transient int modCount = 0;

    /**
     * 要分配的数组的最大大小。
     * 一些vm在数组中保留一些头字。
     * 尝试分配较大的数组可能会导致OutOfMemoryError:请求的数组大小超过VM限制
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 构造具有指定初始容量的空列表。
     * @param  initialCapacity  the initial capacity of the list(列表的初始容量)
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative(如果指定的初始容量为负数-->参数不合法异常)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                    initialCapacity);
        }
    }

    /**
     * 构造一个初始容量为10的空列表,其在未添加元素时默认为容量为0
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 将指定的元素追加到此列表的末尾。
     * @param student element to be appended to this list(要附加到此列表的元素)
     * @return 返回一个布尔类型,表示是否添加成功
     */
    public boolean add(Student student) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = student;
        return true;
    }

    /**
     *确保内部容量
     * @param minCapacity 最小容量(刚好存入)
     */
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    /**
     * 计算容量
     * @param elementData 存储ArrayList元素的数组缓冲区。
     * @param minCapacity 所需的最小容量
     * @return 计算后的最小容量
     */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	//当调用的是无参构造器,且第一次添加元素时,进入该if分支
        if (elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA ) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    /**
     *确保许可容量
     * @param minCapacity 所需的最小容量
     */
    private void ensureExplicitCapacity(int minCapacity) {
    	//进行了修改操作,记录一下内部结构更改次数
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     *增加容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。
     * 当扩容后的容量大于MAX_ARRAY_SIZE(要分配的数组的最大大小)时,调用hugeCapacity(minCapacity)方法
     * @param minCapacity the desired minimum capacity(期望的最小容量)
     */
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //预分配冗余空间的方式来减少内存的频繁分配,采用了空间换时间思想
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    /**
     *大容量
     * 当minCapacity(期望的最小容量)大于MAX_ARRAY_SIZE(数组的最大大小)时,
     * 将Integer.MAX_VALUE(整数的最大值)作为扩容容量,不大于则将MAX_ARRAY_SIZE(数组的最大大小)
     * 作为扩容容量
     * @param minCapacity 期望的最小容量
     * @return Integer.MAX_VALUE(整数的最大值) MAX_ARRAY_SIZE(数组的最大大小)
     */
    private static int hugeCapacity(int minCapacity) {
        //当两个比较大的数相加时,可能存在结果为负数的情况,当出现这种情况时,抛出内存溢出错误
        if (minCapacity < 0)
            throw new OutOfMemoryError(); //内存溢出
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }
}

二、实现过程

1.创建ArrayList对象

通过创建对象,调用相应的构造方法,完成初始化操作

1.1 当调用无参构造器

	/** 
     * 构造一个初始容量为10的空列表,其在未添加元素时默认为容量为0
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

从上面的代码可以知道,其在未添加数据时的容量其实等于0

1.2 当通过构造器传入初始容量

	/**
     * 构造具有指定初始容量的空列表。
     * @param  initialCapacity  the initial capacity of the list(列表的初始容量)
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative(如果指定的初始容量为负数-->参数不合法异常)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                    initialCapacity);
        }
    }

从这里可以发现,EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA都是空数组,这是为了将ArrayList中默认的空数组与我们自己创建的初始容量为0的空数组区分开来。

2 调用add方法

	/**
     * 将指定的元素追加到此列表的末尾。
     * @param student element to be appended to this list(要附加到此列表的元素)
     * @return 返回一个布尔类型,表示是否添加成功
     */
    public boolean add(Student student) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = student;
        return true;
    }

调用add方法,如果没有传入初始容量,其默认容量则为0;所以便须确保其内部数组的容量

	/**
     *确保内部容量
     * @param minCapacity 最小容量(刚好存入)
     */
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

通过对内部数组容量与理想最小容量进行比较,当为默认初始空数组,则直接将其理想最小容量变为10,不是则直接将size+1(刚好可以存下)作为理想最小容量

	/**
     * 计算容量
     * @param elementData 存储ArrayList元素的数组缓冲区。
     * @param minCapacity 所需的最小容量
     * @return 计算后的最小容量
     */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA ) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

当理想最小容量大于内部数组容量,则需对内部数组进行扩容操作

	/**
     *确保许可容量
     * @param minCapacity 所需的最小容量
     */
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

扩容操作:
oldCapacity + (oldCapacity >> 1) —>扩容打了原来的1.5倍,
在这便存在一个问题,当我的扩容后的容量已经超过了数组的最大容量

	/**
     *增加容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。
     * 当扩容后的容量大于MAX_ARRAY_SIZE(要分配的数组的最大大小)时,调用hugeCapacity(minCapacity)方法
     * @param minCapacity the desired minimum capacity(期望的最小容量)
     */
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //预分配冗余空间的方式来减少内存的频繁分配,采用了空间换时间思想
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

当扩容后的容量大于数组最大容量时,如果我的理想最小容量不大于数组的最大大小,就将数组的最大大小作为扩容后的容量;如果我的理想最小容量大于数组的最大大小,则将整数的最大值作为扩容后的容量。

	/**
     *大容量
     * 当minCapacity(期望的最小容量)大于MAX_ARRAY_SIZE(数组的最大大小)时,
     * 将Integer.MAX_VALUE(整数的最大值)作为扩容容量,不大于则将MAX_ARRAY_SIZE(数组的最大大小)
     * 作为扩容容量
     * @param minCapacity 期望的最小容量
     * @return Integer.MAX_VALUE(整数的最大值) MAX_ARRAY_SIZE(数组的最大大小)
     */
    private static int hugeCapacity(int minCapacity) {
        //当两个比较大的数相加时,可能存在结果为负数的情况,当出现这种情况时,抛出内存溢出错误
        if (minCapacity < 0)
            throw new OutOfMemoryError(); //内存溢出
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }

三 总结

我们需要知道,newCapacity(扩容后的数组容量)与minCapacity是有区别的。

四 理解上的不足

源码中的modCount,对其的了解不够深入

/**
     * 记录了结构性改变的次数。
     * 结构性改变指的是那些修改了列表大小的操作,在迭代过程中可能会造成错误的结果。
     * madCount交由迭代器(Iterator)和列表迭代器(ListIterator)使用,
     * 当进行next()、remove()、previous()、set()、add()等操作时,
     * 如果madCount的值意外改变,
     * 那么迭代器或者列表迭代器就会抛出ConcurrentModificationException异常。
     */
    protected transient int modCount = 0;

MAX_ARRAY_SIZE的计算,Integer.MAX_VALUE - 8;为什么要减去8

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值