学习:从源码角度分析 ArrayList 和 add(E e)

尝试分析ArrayList源码。


首先看一下他的 类图 的结构
在这里插入图片描述
可序列化、可克隆、可迭代、可随机访问。
可随机访问这个接口,就是说for循环遍历集合会优于迭代器遍历。

间接继承了collection 和 list 接口,一般常用 list 接口的方法。常用 list 通过多态引用 ArrayList 对象。

上图就是说 ArrayList 中有上方直接或间接父类、接口中的功能的集合体

来看 ArrayList 中的部分参数,源码:

	//默认的初始容量
    private static final int DEFAULT_CAPACITY = 10;
    //一个空数组
    private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
    //空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
    //序列化时被忽略,ArrayList 集合底层用于 保存数据 的 对象数组
    transient Object[] elementData;
    //有效元素个数
    private int size;
    //数组容量最大值 int 最大值(2147483647) - 8 
    private static final int MAX_ARRAY_SIZE = 2147483639;

关于最大容量为什么少 8 个容量,

some VMs reserve some header words in a array
某些虚拟机会在一个数组中保存一些头信息

这是作者说的,如果尝试定义大数组时可能造成内存溢出错误 (OutOfMemoryError),
所以最大容量给你限制了 8 个。但我实际测试的时候可以创建的大小很小,要远小于这个最大容量,要不然heap空间不够,可以通过设置来增加 虚拟机的内存。

接着往下看构造函数,源码:

	//无参构造函数
    public ArrayList() {
    	//直接将底层数组初始化为一个空的数组,即:你不用集合,我就不站你空间!
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
	//自定义初始化容量
	public ArrayList(int initialCapacity) {
		//如果初始化的容量大于0
        if (initialCapacity > 0) {
        	//就创建你指定大小的对象数组
            this.elementData = new Object[initialCapacity];
        } else {
        	//如果初始化容量小于 0,抛异常:不合法的容量大小
            if (initialCapacity != 0) {
                throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
            }
			//最后一种情况就是初始化的容量是 0 ,就直接把创建好的空数组引用给保存数据的数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    
	//根据一个 Collection接口 体系内的集合来初始化一个数组。
    public ArrayList(Collection<? extends E> c) {
    	//调用初始化数组的,toArray 方法将其直接转换成数组对象
        this.elementData = c.toArray();//Collection 实现类中,会有这个方法的实现
        //将数组对象的长度付给成员变量 size (元素个数计数器)
        if ((this.size = this.elementData.length) != 0) {//如果元素个数不是 0 
        	//这里判断了一下底层数组的类型是不是 Object 对象数组
            if (this.elementData.getClass() != Object[].class) {
            	//如果不是的话就将其保存在一个Object对象数组中
                this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);
            }
        } else {
        	//如果传入的集合的元素个数就是 0,那么就直接把定义好的空数组交给底层对象数组 
            this.elementData = EMPTY_ELEMENTDATA;
        }

    }

单独解释一下上面的这个 if 判断

if (this.elementData.getClass() != Object[].class) 

这个 If 判断,当之前的 toArray 方法调用后,有可能返回的不是一个Object[] 引用,可能是比如: String[] 或者某个类行的数组,即:

String[] s = new String[1];
Object[] o = s;

上面的 o 不是 Object[] 类型的引用。
所以避免这种情况,产生了这个判断,来确定一下底层数组的引用类型为 Object[] ,如果不是这个类型就将原数组中的数据,保存在 Object[] 类型的数组中。这里只知道他是这么处理的,至于为什么要这么处理,这么处理之后会对以后的代码产生什么影响我就不知道了。。。要是有大神明白,恳请提点!!!

接下来就是常用方法了

add(E e) 源码:

    public boolean add(E e) {
    	//一个计数操作,记录对集合修改的次数,在迭代器中作为校验	
        ++this.modCount;
        //调用真正的添加方法,参数:1.要添加的对象  2.底层数据对象数组  3.当前数组元素个数
        this.add(e, this.elementData, this.size);      
        return true;
    }
    
	private void add(E e, Object[] elementData, int s) {
		/*
			首先要判断一下数组大小是否可以再容纳一个数据
			如果当前数组中有效元素个数 s 已经跟数组的容量大小一致时,
			即,底层数组已经没有空间容纳将要保存的数据 e 了
			此时需要扩容(扩容方法 grow 接下来单独解析)。
		*/
        if (s == elementData.length) {
            elementData = this.grow();//扩容方法
        }
		//到这里时 数组的容量是足够添加一个新数据的,那么就将数据添加到数组当中
		//位置的索引是有效元素 s 的值,意味着 索引位 0 ~ s-1  的元素都是有效元素
        elementData[s] = e;
        //添加完元素之后,让有效元素的个数加一
        this.size = s + 1;
    }

grow() 方法

	//add调这个方法
	private Object[] grow() {
		/*
	       	将 有效元素个数+1 传入到 grow(int) 方法中,
        	这个方法接收的参数是最小容量 minCapacity,
        	这里 +1 意味着扩容至少要扩容一个,避免 size 是 0 的情况,扩容了 0 这种情况,
        	也可以这样理解:我现在要添加一个数据,那么你容量不够用了,要扩容的话至少要扩容一个保证我要添加的这个数据能插入进去
        */
        return this.grow(this.size + 1);
    }
    
    //这个方法来真正进行扩容
	private Object[] grow(int minCapacity) {
		//调用了两个方法:Arrays.copyOf 和 newCapacity
		//Arrays.copyOf 是用来将原数组复制到新建的数组中,这个新建的数组大小是通过 newCapacity 方法获得的
		//newCapacity 根据最小容量来计算扩容后的数组容量
        return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity));
    }
    
	//这个方法根据一个最小容量来获取扩容大小
    private int newCapacity(int minCapacity) {
    	//获取未扩容的数组容量大小
        int oldCapacity = this.elementData.length;
        //新数组的容量是老数组的容量的 1.5倍 即 old + (old/2)   右移一位相当于 除2 取整
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果 新容量 - 最小容量 不为正数
        if (newCapacity - minCapacity <= 0) {
        	//判断当前的数组是否是没使用过的数组,这里就使用两个 空数组加以区分,一个是调用无参构造时elementData引用的默认的空数组,一个是初始化传的容量值为 0 时elementData引用的空数组
            if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            	//从传进来的最小容量 和 10 之间选择一个最大的,作为新容量返回,相当于第一次添加数据的时候,才为数组定义大小(最小就是 10 )
                return Math.max(10, minCapacity);
            } else if (minCapacity < 0) {
            	//如果最小容量小于0 就抛异常,内存溢出
                throw new OutOfMemoryError();
            } else {
            	//如果以上情况都不是,那么至少要返回的新容量足以添加当前需要插入的数据,
            	//至少容量扩容一个,足以让我把当前的数据添加进集合
                return minCapacity;
            }
        } else {
        	//如果计算的得到的 新容量-最小容量 > 0
        	/*
        		三目运算符:
        			判断 新容量 <= 默认的数组最大容量(MAX_ARRAY_SIZE)
        					直接返回这个计算得到的 newCapacity
        				 新容量 > 默认的数组对大容量
        				 	调用 hugeCapacity 方法
        	*/
            return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
        }
    }
    
	//当 新容量 大于 MAX_ARRAY_SIZE 调用此函数
    private static int hugeCapacity(int minCapacity) {
        //最小容量小于0,抛异常
        if (minCapacity < 0) {
            throw new OutOfMemoryError();
        } else {
        	//当最小值是大于 MAX_ARRAY_SIZE 的时候 返回 MAX_ARRAY_SIZE + 8
        	//否则返回 MAX_ARRAY_SIZE
            return minCapacity > 2147483639 ? 2147483647 : 2147483639;
        }
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值