深入理解ArrayList集合的初始容量和初始容量为0的两种扩容机制(1.8版本)
网络上对于ArrayList集合的空参构造是否为0,存在不同的的看法。对此,分析了源码,有以下见解:
1.空参构造,集合初始容量必定为0,添加一个元素,扩容为10。
2.有参构造,参数为0和集合长度为0时,初始容量为0,添加一个元素扩容为1,再添加一个元素扩容为2,再添加一个元素扩容
为3,再添加一个元素扩容为4,再添加一个元素扩容为6...
- 版本不同
- ArrayList的三种构造方法
- ArrayList()的初始容量和扩容
- ArrayList(int initialCapacity)的初始容量扩容
- ArrayList(Collection( extends E) c)(尖括号显示不出来,用()代替)
- ArrayList集合的扩容公式
版本不同
JAVA对于ArrayList集合进行了优化,目前所知,优化了空参构造的初始容量和扩容公式,至于是1.7还是1.8版本开始,
就不清楚了。在1.6版本之时,该集合的空参构造的初始容量,确实是10,源码自己找。
该文的源码以JDK1.8为主。
ArrayList的三种构造方法
public ArrayList(int initialCapacity)
public ArrayList()
public ArrayList(Collection<? extends E> c)
public ArrayList()的初始容量和扩容
首先,分析空参构造的初始容量。
在1.8里面定义了三个Object[]:
1.用于空实例的共享空数组实例。
private static final Object[] EMPTY_ELEMENTDATA = {}
2.用于默认大小的空实例的共享空数组实例。
该数组与EMPTY_ELEMENTDATA区别,但都为空,以便知道当添加第一个元素时,该数组应该于何时扩容多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
而这两个数组,有什么关系呢?
第一个数组运用于有参构造方法的参数为0或者集合长度为0的时候,而第二个数组运用于空参构造的时候。
这两个数组一定要区别,对于以后的扩容造成影响。
ArrayList的底层是由(transient Object[] elementData)实现的; 该数组是存储ArrayList元素的数组缓冲区。ArrayList的容量是这个数组缓冲区的长度。
任何的elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空集合,当添加第一个元素时,将被扩展到DEFAULT_CAPACITY。
- 初始容量
要知道空参构造的初始容量,要对空参构造的源码进行分析:
public ArrayList() {
this.elementData =
DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这样看空参构造,初始容量就是0。
- 扩容机制
那么当添加一个元素的时候,集合的容量会扩容到默认容量为10,也可能为大于10的值,视情况而定。一般默认一次只添加一个元素。
private int newCapacity(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
//这里就定义了当
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组添加元素的情况,若是第一次添加:1.只添加一个元素,
//那么容量就扩容为DEFAULT_CAPACITY=10;2.要是添加的元素大于该默认容量,则以该元素个数为
//扩容后的容量。
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
public ArrayList(int initialCapacity)的初始容量和扩容
- 初始容量
这里就讨论当参数为0的时候,该集合的初始容量也为0。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//当该参数为0,底层数组为EMPTY_ELEMENTDATA,那么该数组的长度为0,集合初始容量则为0。
} else {
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
-
扩容机制
加进一个元素,扩容为1。
源码分析:
//add源码
public boolean add(E e) {
modCount++;
add(e, elementData, size);//调用add(E e, Object[] elementData, int s)方法
return true;
}
//添加一个元素,调用此方法
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();// 数组长度为0,继续调用grow()方法
elementData[s] = e;
size = s + 1;
}
private Object[] grow() {
return grow(size + 1);//grow()调用grow(1)
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
//扩容方法newCapacity(minCapacity)
}
//扩容
private int newCapacity(int minCapacity) {
int oldCapacity = elementData.length;//oldCapacity=0
int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity=0
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA空参构造的数组;
//抱歉这是为了空参构造设计的,参数为0和集合长度为0对应的数组是EMPTY_ELEMENTDATA。
if (minCapacity < 0)
throw new OutOfMemoryError();
return minCapacity;//上述条件都不符合,那么就返回1.
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity//当newCapacity - minCapacity > 0,且小于最大的数组长度,就返回原数组的1.5倍
: hugeCapacity(minCapacity);
}
所以当添加一个元素进去时,该集合扩容为1。
public ArrayList(Collection(? extends E) c)
该构造方法与 ArrayList(int initialCapacity)方法有一样的地方:当参数集合长度为0时,该集合的初始容量也为0。
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// defend against c.toArray (incorrectly) not returning Object[]
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
//当该集合为空时,同 ArrayList(int initialCapacity)方法的参数为0情况,集合的初始容量为0。
扩容情况参照ArrayList(0)的情况,同样。
ArrayList集合的扩容公式
1.6版本的扩容公式:
数组原长度*3/2+1
.
1.6之后的扩容公式:
oldCapacity + (oldCapacity >> 1)
因为位运算快。
之所以写此博文,是因为,看见一个拿着1.8版本的源码,睁眼说瞎话,说空参构造的初始容量为10,这简直就是害人。不平之下
, 按照自己对于源码的见解,进行分析,望能帮助到需要之人。
若有不足之处,望批评指正。