在JDK1.8中,ArrayList扩容机制Increments modCount与起始化讲解

  • Post author:
  • Post category:其他


在ArrayList中,起始化方式有两种:


1.调用无参的构造方法:

public ArrayList() {  //无参构造方法
			this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
	//将ArrayList起始化为一个名叫DEFAULTCAPACITY_EMPTY_ELEMENTDATA的对象
	}

↓ 我们找到DEFAULTCAPACITY_EMPTY_ELEMENTDATA

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//	其实就是创建了一个名为DEFAULTCAPACITY_EMPTY_ELEMENTDATA的Object[]
//	注意:为内容为{},可以理解为长度为0,但不是NUll;


2.调用有参的构造方法:

public ArrayList(int initialCapacity) {//有参构造方法
		if (initialCapacity > 0) {	//如果传入的参数大于0
			this.elementData = new Object[initialCapacity];
			//按照传入的参数起始化创建一个相应长度的Object[]
		} else if (initialCapacity == 0) {//如果传入的参数等于0
			this.elementData = EMPTY_ELEMENTDATA;
			//起始化创建一个长度为0的Object[],等同与无参构造
		} else {		//如果传入的参数等于0
			throw new IllegalArgumentException("Illegal Capacity: "+
											   initialCapacity);
			//抛一个名为非法参数异常
		}
	}

在明白了构造之后,我们来看看扩容。

首先扩容机制会在调用add方法添加元素时才会触发,我们阅读源码,会发现,扩容机制分为下列阶段:

  1. add(E e)
  2. ensureCapacityInternal(int minCapacity)
  3. ensureExplicitCapacity(int minCapacity)
  4. grow(int minCapacity)


    下面详细分析每个方法的作用:



    一:add方法
	// 添加元素
	public boolean add(E e) {
		
		ensureCapacityInternal(size + 1);
		// 按照元素个数+1,确认数组容量是否够用
		elementData[size++] = e;
		//将数组第size位置添加为该元素
		return true;
	}

看完add,我们明白了,要想搞明白是怎么进行扩容的,还得看看ensureCapacityInternal()的内容


二:ensureCapacityInternal方法

private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
//这里是调用了ensureExplicitCapacity方法,而里面又调用calculateCapacity方法
	}

在这里不要急,我们先进入calculateCapacity()方法阅读一下

private static int calculateCapacity(Object[] elementData, int minCapacity) {
		if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		//判断数组是否起始化为{}
			return Math.max(DEFAULT_CAPACITY, minCapacity);
			//如果是,则返回10
		}
		return minCapacity;
		//返回数组需要的最小容量
	}

我们发现在alculateCapacity()方法中返回了一个容量值,其作为参数传入了ensureExplicitCapacity()之中,所以进入了下一步:


三: ensureExplicitCapacity方法

private void ensureExplicitCapacity(int minCapacity) {//判断当前容量是否够用
		modCount++;

		// overflow-conscious code
	if (minCapacity - elementData.length > 0)
	//判断当前容量是否够用
		grow(minCapacity);
		//如果不够,进去则进人grow
	}

在这里,我们终于来到了最关键的一步,也就是如果容量不够,则需要进入grow()方法进行扩容。


四: grow方法

private void grow(int minCapacity) {
		// overflow-conscious code
		int oldCapacity = elementData.length;
		//旧的容量为当前数组长度
		int newCapacity = oldCapacity + (oldCapacity >> 1);
		//新的容量为当前数组长度的1.5倍
		if (newCapacity - minCapacity < 0)
		//判断当前的容量是否不足于需要的最小容量
			newCapacity = minCapacity;
			//如果不足,则新容量为当前需要的最小容量
		if (newCapacity - MAX_ARRAY_SIZE > 0)
		//判断现在的新容量是否大于int的最大容量-8
			newCapacity = hugeCapacity(minCapacity);
			//如果大于,则进去hugeCapacity方法返回新的容量
		elementData = Arrays.copyOf(elementData, newCapacity);
		//按照最新容量复制数组,实现扩容
	}

其中这一行代码为扩容的关键:


int newCapacity = oldCapacity + (oldCapacity >> 1);


oldCapacity >> 1表示做向右移一位运算,等同于取二分之一。

所以这一行代码的意义即为:去原数组长度再加上其的一半,总共1.5倍原数组长度为新长度。即数组容量按照1.5倍增长。

然后就是调用copyOf()方法,将旧数组的新容量个元素,copy到一个新数组中。


到这里扩容就完成了。


我们也可以顺带看一下hugeCapacity方法:

 private static int hugeCapacity(int minCapacity) {
		if (minCapacity < 0) 
		// 如果当前需要的最小容量小于0
			throw new OutOfMemoryError();
			//抛出内存溢出的错误
		return (minCapacity > MAX_ARRAY_SIZE) ?
		//返回当前最小容量与MAX_ARRAY_SIZE谁大
			Integer.MAX_VALUE :
			//最小容量大,返回Integer.MAX_VALUE ,即2147483647
			MAX_ARRAY_SIZE;
			//否则返回MAX_ARRAY_SIZE,即2147483639
	}

在弄明白扩容原理后,我们再回过头看一下两种构造方式,都会如何实现集合元素的添加:


1.如果调用无参的构造方法:


例如

ArrayList<String> list = new ArrayList<String>();

则当第一次添加元素时:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
		if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		//这个时候满足了这个条件
			return Math.max(DEFAULT_CAPACITY, minCapacity);
		}
		return minCapacity;
	}

所以会返回DEFAULT_CAPACITY,即返回10.而10又作为参数,传入了ensureExplicitCapacity方法

private void ensureExplicitCapacity(int minCapacity) {
		modCount++;
	if (minCapacity - elementData.length > 0)
	//判断当前容量是否够用
		grow(minCapacity);
		//如果不够,进去则进人grow
	}

此时minCapacity为10,elementData.length为0,所以又会进入grow方法

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);
	}

此时oldCapacity 就为0,newCapacity经过计算也为0,minCapacity还是为10,满足了newCapacity – minCapacity < 0条件,newCapacity = minCapacity,新的容量newCapacity就为10,

elementData = Arrays.copyOf(elementData, newCapacity),

所以扩容后的新数组长度就为10了,第一次扩容就结束了。

当需要传入第11个元素的时候,就会发生第二次扩容,按照我们上面讲解,第二次扩容即为10的1.5倍,即为15,后面的情况以此类推。


2.如果调用有参的构造方法:


例如

ArrayList<String> arrayList = new ArrayList<String>(100);

有了上面的讲解,这里就非常简单了:

当需要传入第101个元素的时候,就会发生扩容,按照我们上面讲解,扩容即为100的1.5倍,即为150,后面的情况以此类推



版权声明:本文为m0_50666031原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。