集合基本上面试时面试必问的,我一个同事曾经面试时就被问过集合的默认长度是多少。下面我们就ArrayList集合的长度来说说指定初始容量的事。
List<Persion> list = new ArrayList<Persion>();
相信大部分在使用集合是,都是类似的声明一个集合,然后用add、remove等方法对集合进行操作,而且因为它是自动管理长度的,所以不用我们特别费心超长的问题,这确实是一个非常好的优点,但也有我们必须要注意的事项。下面我以ArrayList为例深入了解下java是如何实现自动长度的动态管理,从add方法开始,代码如下(我的是jdk1.7):
public boolean add(E paramE)
{
//扩展长度
ensureCapacityInternal(this.size + 1);
//追加元素
this.elementData[(this.size++)] = paramE;
return true;
}
我们知道ArrayList是一个大小可变的,但它在底层使用的是数组存储(也就是elementData变量),而且数组是定长的,要实行动态长度必然要进行长度的扩展,代码如下:
private void ensureCapacityInternal(int paramInt) {
this.modCount += 1;
if (paramInt - this.elementData.length > 0)
grow(paramInt);
}
private void grow(int paramInt)
{
//上次(原始)定义的数组长度
int i = this.elementData.length;
int j = i + (i >> 1);
if (j - paramInt < 0)
j = paramInt;
if (j - 2147483639 > 0) {
j = hugeCapacity(paramInt);
}
this.elementData = Arrays.copyOf(this.elementData, j);
}
private static int hugeCapacity(int paramInt) {
if (paramInt < 0)
throw new OutOfMemoryError();
return paramInt > 2147483639 ? 2147483647 : 2147483639;
}
我们看到新数组的长度计算方法,并不是增加一个元素,elementData 的长度就加1,而是在达到elementData 长度的临界点时,才将elementData 扩容1.5倍(i + (i >> 1))右移1位相当于除以2,这样有什么好处呢?避免了多次调用copyOf方法的性能开销,否则没加一个元素都要扩容一次,那性能将会非常糟糕。
知道了扩容原则,还有一个问题:elementData 的默认长度是多少呢?答案是10,比如new ArrayList(),则elementData 的初始长度是10.代码如下:
//无参构造,通常用的最多的就是这个
public ArrayList()
{
//指定数组长度的有参构造
this(10);
}
//指定数组长度的有参构造
public ArrayList(int paramInt)
{
if (paramInt < 0) {
throw new IllegalArgumentException("Illegal Capacity: " + paramInt);
}
this.elementData = new Object[paramInt];
}
默认初始化时声明了一个长度为10的数组,在通过add方法增加弟11个元素时,ArrayList类就自动扩展了,新的elementData 数组长度是10+(10>>1),也就是15,可以看出,如果不设置初始容量,系统就按照1.5倍的规则扩容,每次扩容都是一次数组的拷贝,如果数据量很大,这样的拷贝会非常耗费资源,而且效率非常低下。如果我们已经知道一个ArrayList的可能长度,然后对ArrayList设置一个初始容量则可以显著提高系统性能。