源码解析——ArrayList

  对于Collection集合类,网上常见的结构图如下:

 

上图显示了常用的几个集合工具类的相互关系和结构组成,但实际上远没有那么简单,因此我从ArrayList开始,深入底层源码,慢慢将集合工具类的大体框架和具体实现梳理出来,帮助自己深入理解集合工具类。

首先ArrayList作为List家族的一员,首先来梳理一下List家族的具体结构组成:

所以作为我们最常用的ArrayList类,其直接父类是AbstractList,并且实现了 List接口、RandomAccess接口、Cloneable接口以及java.io.Serializable接口。List接口就不用说了,RandomAccess接口里面并没有什么方法,只是作为一个标志接口,用于区分ArrayList和LinkedList,从而选择合适的遍历方式。具体请参照:ArrayList为什么需要实现RandomAccess接口?

而实现Cloneable接口主要是实现Clone方法,使得ArrayList可以进行克隆操作,获得一个一模一样的ArrayList,但是两个ArrayList的引用不一样。而实现Serializable接口则是使得ArrayList可以序列化。

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

接下来再来看一下ArrayList的成员变量和常量:

private static final long serialVersionUID = 8683452581122892189L;
//验证版本是否一致,用于反序列化的
			
private static final int default_capacity = 10;
//默认初始容量为10
			
private static final Object[] empty_elementdata = {};
//默认空数组,当数组初始化大小为0时使用
			
private static final Object[] defaultcapacity_empty_elementdata = {};
//默认空数组,当数组初始化大小为10的时候使用,当添加第一个元素时,该数组
//便会被初始化为默认大小,从而与empty_elementdata区别
			
transient Object[] elementData;
//这是ArrayList用于存放元素的具体位置,transient表示该区域不能被反序列化
			
private int size;
//集合数组大小

此处serialVersionUID有些难以理解,特别是之前没有接触过序列化与反序列的(比如博主...),因此我就将它作为一个标签,主要是在序列化和反序列的时候用到,而另外一个比较难以理解的是empty_elementdata和defaultcapacity_empty_elementdata两个私有静态常量数组(源码这两个数组名字是全大写的,因为是常量,但此处为了方便理解,我暂时改为小写)难以理解就暂且放下,继续往下看。从这些成员变量和常量中就基本可以看出ArrayList是怎么实现的。底层其实也就是普通数组,只是它将数组本身和数组大小分开了。

接下来继续看它的构造器:

public ArrayList(int initialCapacity){
//构造器1,如果传入的参数是数组容量,如何初始化ArrayList
    if(initialCapacity > 0){
	this.elementData = new Object[initialCapacity];
    }else if(initialCapacity == 0){
	this.elementData = empty_elementdata;
    }else{
	throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity);
	//如果传入的初始化容量小于零,那就抛出非法争议异常,提示容量赋值非法
	}
    }
			
public ArrayList(){
//构造器2,如果不传入参数,那就初始化为空的数组
	this.elementData = defaultcapacity_empty_elementdata;
}
			
public ArrayList(Collection<? extends E> c){
//构造器3,如果传入的参数是一个集合,如何初始化ArrayList
	elementData = c.toArray();
        //直接用集合c本身的方法转化为普通数组赋给ArrayList集合中的成员数组
	if((size = elementData.length) != 0){
		if(elementData.getClass() != Object[].class)
		    elementData = Arrays.copyOf(elementData,size,Object[].class);
		//如果转化过来的数组成员不是Object类,则使用复制的方法全都转化为Object类
		//作用就是方便后面进行相关操作
	}else{
	    this.elementData = empty_elementdata;
            //如果参数数组为空,则直接用成员变量初始化
	}
}

从上面可以看出,ArrayList类总共提供了三个构造器,分别是无参构造器、参数为集合数组初始大小的构造器以及参数是其他集合的构造器。另外还有一个特别之处是其他两个构造器如果接收的参数是空或者零的话都是将empty_elementdata常量数组赋给成员变量数组的,只有无参构造器直接将defaultcapacity_empty_elementdata常量数组赋给成员变量数组,为什么呢?继续往下看。

接下来看一下ArrayList类的核心方法,自动扩容:

public void ensureCapacity(int minCapacity){
//确保容量,此方法是公用方法,也就是说可以从外界直接调用的
//ArrayList的扩容方法就这一个是对外开放的,此方法作用是手动扩容
    int minExpand = (elementData != defaultcapacity_empty_elementdata)
		    ? 0
		    : default_capacity;
    //首先判断ArrayList是否初始化,如果成员数组变量是defaultcapacity_empty_elementdata
    //说明还没有初始化,而从成员变量的赋值可以知道,数组一旦初始化就有10容量,因此先查看
    //此数组有没有初始化,初始化了就不能无条件扩容,最小增长为0,
    //没有初始化就说明可以一次性扩10容量
    if(minCapacity > minExpand){
    //判断手动扩容传进来的值是否大于扩充的最小值,也就是说有没有必要
    //按传进来的数值进行相应扩容,如果大于扩容最小值,就开始执行正式扩容
	ensureExplicitCapacity(minCapacity);//调用扩容方法
    }	
}
			
private static int calculateCapacity(Object[] elementData,int minCapacity){
//计算容量
	if(elementData == defaultcapacity_empty_elementdata){//如果集合数组还没有初始化
	    return Math.max(default_capacity,minCapacity);
            //比较传进来的扩容值和初始化时扩容的初始值10,返回最大值
	}
	    return minCapacity;//如果集合数组已经初始化了,那么就返回传入的扩容值
}
			
private void ensureCapacityInternal(int minCapacity){
    //确认内部容量,调用明确容量,并将计算容量方法中获得的最终值当作参数传入
				     ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
}

private void ensureExplicitCapacity(int minCapacity){
//确保明确容量,从此处开始正式扩容,首先modCount增加一
//防止扩容时其他方法对成员数组进行修改造成了数据不同步
	modCount++;
	if(minCapacity - elementData.length > 0)//比较传入值与成员数组的长度,如果大,则扩容
		grow(minCapacity);//执行扩容方法
}
			
private static final int Max_array_size = Integer.MAX_VALUE - 8;
//定义一个常量,即数组容量的限制值,即比Integer最大值小8,我理解为一个缓冲区
			
private void grow(int minCapacity){
	//扩容核心方法
	int oldCapacity = elementData.length;//首先获得原数组的长度
	int newCapacity = oldCapacity + (oldCapacity >> 1);//定义新的容量为原容量的1.5倍
	if(newCapacity - minCapacity < 0)//如果新容量比传入值小
		newCapacity = minCapacity;//直接将新容量定义为传入值
	if(newCapacity - Max_array_size > 0)//如果新容量大到一定程度,接近Integer最大数了
		newCapacity = hugeCapacity(minCapacity);//调用最大容量方法,判断是否超出界线
	elementData = Array.copyOf(elementData,newCapacity);
        //如果没有超,则扩充容量,即创建一个新的数组代替原来的数组
	//新数组里面含有原数组的一切元素,只是数组长度是扩容后的长度
}
			
private static int hugeCapacity(int minCapacity){
//设置最大值限制
	if(minCapacity < 0)//此处的传入值小于零指的是最高位是1,说明已经溢出了
	    throw new OutOfMemoryError();//如果超出最大值,抛出内存溢出错误
	return (minCapacity > Max_array_size)//如果没有超出,则比较是否比限制值大
		? Integer.MAX_VALUE//大的话直接赋值为最大值
		: Max_array_size;//小则赋值为限制值
}

直接这样看比较难理解,先来看一下这些方法是如何执行的吧:

这是我自己的理解,可能也有一些错误,但是总体的步骤应该是这样的,因此一次扩容还是比较复杂的,因此ArrayList提供了一个对外的方法,即图中的ensureCapacity方法,可以直接进行比较大的扩容,如果大体确定一个集合数组会容纳多少元素,可以直接先执行此方法。这样就免去了每一次增加一个元素就要执行一次扩容操作这样繁杂的操作。

当然,数组扩容也不是真的一个一个扩,从源代码中grow方法中可以得知,一次扩容大概是原容量的1.5倍左右,至于为何是1.5倍,我的理解是在这个倍数效率最高,如果小了,那就会导致频繁扩容,如果大了,则造成空间的浪费。而且如果传入的值大于这个1.5倍,则按照传入值进行扩容。

除此之外,还有一个值得注意的是,集合数组扩容其实并不是什么高深的技术,就是用一个新的大的数组代替原来旧的老的数组,再把旧数组中的元素全都复制到新数组中去,这样就形成了一个可以自动扩容的集合数组,这也是集合数组和普通数组最大的不同之处。

看到这里,应该就可以明白了为什么要有defaultcapacity_empty_elementdata这个空数组常量了,这个就是判断该集合数组有没有初始化的标志。

剩下的就是一些常用方法,总源代码在下面:

public class ArrayList<E> extends AbstractList<E>
		implements List<E>,RandomAccess,Cloneable,java.io.Serializable
		{
			private static final long serialVersionUID = 8683452581122892189L;
			//验证版本是否一致,用于反序列化的
			
			private static final int default_capacity = 10;
			//默认初始容量为10
			
			private static final Object[] empty_elementdata = {};
			//默认空数组,当数组初始化大小为0时使用
			
			private static final Object[] defaultcapacity_empty_elementdata = {};
			//默认空数组,当数组初始化大小为10的时候使用,当添加第一个元素时,该数组
			//便会被初始化为默认大小,从而与empty_elementdata区别
			
			transient Object[] elementData;
			//这是ArrayList用于存放元素的具体位置,transient表示该区域不能被反序列化
			
			private int size;
			//数组大小
			
			public ArrayList(int initialCapacity){
				//构造器1,如果传入的参数是数组容量,如何初始化ArrayList
				if(initialCapacity > 0){
					this.elementData = new Object[initialCapacity];
				}else if(initialCapacity == 0){
					this.elementData = empty_elementdata;
				}else{
					throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity);
					//如果传入的初始化容量小于零,那就抛出非法争议异常,提示容量赋值非法
				}
			}
			
			public ArrayList(){
				//构造器2,如果不传入参数,那就初始化为空的数组
				this.elementData = defaultcapacity_empty_elementdata;
			}
			
			public ArrayList(Collection<? extends E> c){
				//构造器3,如果传入的参数是一个集合,如何初始化ArrayList
				elementData = c.toArray();
				//直接用集合c本身的方法转化为普通数组赋给ArrayList集合中的成员数组
				if((size = elementData.length) != 0){
					if(elementData.getClass() != Object[].class)
						elementData = Arrays.copyOf(elementData,size,Object[].class);
					//如果转化过来的数组成员不是Object类,则使用复制的方法全都转化为Object类
					//作用就是方便后面进行相关操作
				}else{
					this.elementData = empty_elementdata;
					//如果参数数组为空,则直接用成员变量初始化
				}
			}
			
			public void trimToSize(){
				//类似剪枝吧,把elementData没用的剩余空间清除
				modCount++;//不知道这个有什么用....
				if(size < elementData.length){
					elementData = (size =&#
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值