ArrayList的扩容规则是变成原来最大容量的1.5倍+1
具体为什么,现在看一下源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal是判断是否要扩容的方法,下面是源码:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private transient Object[] elementData;
elementData是存放元素的对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
EMPTY_ELEMENTDATA是空数组,表示现在ArrayList是空的
ensureCapacityInternal中首先是判断现在的ArrayList是不是空的,如果是空的,minCapacity就取默认的容量和传入的参数minCapacity中的大值
然后调用ensureExplicitCapacity方法,
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount是fail fast机制,在jdk1.6之前都是有volatile来修饰的,尽可能的让并发访问非安全的集合对象时尽快的失败抛出异常,让程序员修改代码。
在jdk1.7中去掉了volatile修饰,因为感觉没有必要为非线程安全集合浪费效率,在jdk1.5开始就提供了线程安全的集合类,在多线程环境下就应该使用线程安全的集合。
接着看,如果minCapacity的值大于add数据之前的大小,就调用grow方法,进行扩容,否则什么也不做。
下面是具体看出来扩容机制的大小增长规则了:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
注意:这里传过来的minCapcatiy的值是size+1,能够实现grow方法调用就肯定是(size+1)>elementData.length的情况,所以size就是初始最大容量或上一次扩容后达到的最大容量,所以才会进行扩容。
newCapacity=oldCapacity+(oldCapacity>>1),这里就是扩容大小确定的地方,相当于新的最大容量是 size+1+size/2 相当于原来的1.5倍然后加1
==============================================================================================================================
这样每次size达到容量后,再插入数据就会造成扩容,默认的容量大小是10,如果我们现在连续插入17的对象,就会扩容两次,第一次是在插入第11个对象时,第二次是在插入第17个对象时扩容,这样会频繁进行数组的拷贝,效率影响很大。那么我们在插入数据数量已知的情况,可以调用ensureCapacity方法。
下面是方法源码:
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != EMPTY_ELEMENTDATA)
// any size if real element table
? 0
// larger than default for empty table. It's already supposed to be
// at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
为什么要先判断先判断elementData!=EMPTY_ELEMENTDATA呢?现在看一下ArrayList的无参构造函数
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
这里看出来ArrayList在没有指定初始化容量的时候elementData就是一个空对象数组,没有任何对象元素,也就是容量为0,没有分配空间。
可以再看一下add方法中,调用的ensureCapacityInternal方法中判断elementData == EMPTY_ELEMENTDATA 是否为空,如果为空,minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);,minCapacity设置为其中的大值,在默认是空的时候,初次调用add方法传入的参数是1,也就是这里的minCapacity就是1,现在minCapcity变成了DEFAULT_CAPACITY 也就是10,下面会调用grow方法,然后执行 elementData = Arrays.copyOf(elementData, 10);下面注意拷贝方法:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
执行了第一条语句后,copy=new Objecet[10]了,返回的结果数组大小就是10了, ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10个对象空间。
上面分析过程也包含了扩容的步骤了。
当我们已经确定了要插入的对象的数目(并不是在创建ArrayList之前就知道有多少对象要插入的情况),就应该首先调用ensureCapacity来一次性扩容到可以容得下要插入的对象个数,这样就避免的多次进行数组拷贝的情况,提高了效率,算是优化吧
当然,我们在创建ArrayList之前就知道有多少对象要插入就使用有参构造。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章