数组最大的优势是根据下标获取数据的时间复杂度为O(1)。
但是数组有一个缺点就是需要一段连续的内存空间。而且数组大小不可以改变。如果数组满了,还要继续存储,那只能扩容数组。
有人可能会说,Java中java.util.ArrayList底层就是数组实现的,从来没有管过数组大小的问题啊。
其实是java.util.ArrayList自己实现了自动扩容。
现在我们就来分析一下,java.util.ArrayList 是如何自动扩容的。
源码采用的是JDK1.8
我们一般初始化一个ArrayList的方法是用它的构造方法
ArrayList list = new ArrayList();
ArrayList有3个构造方法,如下:
第一个不带参数的构造函数
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
注释这里虽然说了用初始化容量10构建一个空集合,但是这里默认是一个空的数组。难道JDK源码注释错了?大概率不会错吧,我们继续看。
我们看下 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
注释大概的意思是,DEFAULTCAPACITY_EMPTY_ELEMENTDATA
用来表示默认大小的空数组实例,跟 EMPTY_ELEMENTDATA
做了区分,为了知道第一次增加元素的时候扩容多少。
第二个构造方法
可以简单理解为,传入一个集合,放到数组中。
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list 传入一个Collection的实现
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
// 转成数组
Object[] a = c.toArray();
// 数组长度大于0
if ((size = a.length) != 0) {
// 如果是ArrayList类型
if (c.getClass() == ArrayList.class) {
// 直接赋值给当前对象
elementData = a;
} else {
// 创建一个size大小的数组,将元素拷贝进去
elementData = Arrays.copyOf(a, size, Object[].class);
}
}
// 数组长度等于0,就实例化一个空数组,注意,这里不是默认长度的数组,而是一个长度为0的空数组
else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
第三个构造方法
这个方法就更好理解了,创建一个指定大小的空数组
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
}
// 这里要注意下,如果传入的是0,则创建的也是一个空的数组,不是默认长度的空数组
else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
add()方法
调用add()方法的时候,首先会判断数组是否还有容量能够存数据,如果数组满了,进行扩容
/**
* Appends the specified element to the end of this list.
* 把元素增加到集合的末尾
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 保证数组的容量能容纳下新元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// minCapacity 最小长度
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果数组是默认大小的空数组,获取 DEFAULT_CAPACITY 和 minCapacity 中较大的值作为扩容容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// DEFAULT_CAPACITY = 10
// 这里,用不带参数的构造方法构造出来的ArrayList,这里会初始化到10
// 当然这里要说明一下,如果用不带参数的构造方法构造出来的ArrayList,第一次就调用了addAll()添加超过了10个元素,那么这里就会取minCapacity了
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 这里你可以先简答的认为这个 modCount 就是数组扩容的次数
modCount++;
// overflow-conscious code
// 如果需要的最小长度比当前数组容量大,需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* 这个就是数组扩容的方法
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 新容量是旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容1.5倍还不够,那就直接赋值为需要的长度
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);
}