我们知道,无论是ArrayList还是StringBuilder,其内部存储机制都是数组。数组在查找的时候有明显的优势,但是在插入或者删除的时候比较麻烦,涉及到扩容机制。下面我们来看一下ArrayList和StringBuilder的扩容机制有什么区别。
1. ArrayList的扩容机制
在了解扩容机制之前,我们先来看一下ArrayList的属性
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] 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 = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
...
}
可以发现,ArrayList的元素是使用Object[]的形式保存的。
接着来看一下ArrayList的构造方法,一共有三种构造方法
第一种方法是ArrayList(int initialCapacity),传入参数为Object[]的容量,具体代码如下
/**
* 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];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
第二种方法是无参构造,详细代码如下
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
第三种方法是ArrayList(Collection<? extends E> c),传入与参数是集合,详细代码如下
/**
* 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
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
下面我们来看一下扩容的代码
/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
在扩容前需要先进行检查,ensureCapacity(int minCapacity)函数用来检查我们传入的容量,假设当前的元素不为空,那么就取基准值为0,如果当前元素为空,那么我们的容量是minCapacity和DEFAULT_CAPACITY中较大的。假如minCapacity >DEFAULT_CAPACITY ,那么我们需要再次确认扩容的容量,假如扩容的容量比当前元素数量要多,那么就进行扩容。
总结一下ensureCapacity(int minCapacity)函数的操作,就是
1)如果当前列表没有元素,那么只有当容量设置大于DEFAULT_CAPACITY的时候才扩容
2)如果当前列表有元素,那么当容量设置大于当前元素数量的时候扩容
接下来就是我们的重头戏,扩容的代码了
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 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;
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
可以发现,扩容的关键操作是 int newCapacity = oldCapacity + (oldCapacity >> 1); ,这句话的意思就是新的容量为1.5倍的旧容量。
并且接下来进行一个判断
1)假如newCapacity小于minCapacity,那么newCapacity=minCapacity
2)假如设置的容量大于设定的最大容量,首先判断一下是否溢出,假如不溢出的话就设置为Integer类型可以达到的最大值
进入到Integer.java中可以找到Integer.MAX_VALUE的取值,这是十六进制表示,其中第一位是符号位,对于正数取0。
/**
* A constant holding the maximum value an {@code int} can
* have, 2<sup>31</sup>-1.
*/
@Native public static final int MAX_VALUE = 0x7fffffff;
2. StringBuilder的扩容机制
StringBuilder继承于AbstractStringBuilder,扩容的代码是在AbstractStringBuilder中的
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
...
}
下面直接来看AbstractStringBuilder的扩容代码
/**
* Ensures that the capacity is at least equal to the specified minimum.
* If the current capacity is less than the argument, then a new internal
* array is allocated with greater capacity. The new capacity is the
* larger of:
* <ul>
* <li>The {@code minimumCapacity} argument.
* <li>Twice the old capacity, plus {@code 2}.
* </ul>
* If the {@code minimumCapacity} argument is nonpositive, this
* method takes no action and simply returns.
* Note that subsequent operations on this object can reduce the
* actual capacity below that requested here.
*
* @param minimumCapacity the minimum desired capacity.
*/
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}
/**
* For positive values of {@code minimumCapacity}, this method
* behaves like {@code ensureCapacity}, however it is never
* synchronized.
* If {@code minimumCapacity} is non positive due to numeric
* overflow, this method throws {@code OutOfMemoryError}.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
中扩容的检查比较简单,基本的逻辑是,首先我们传入的参数minimumCapacity要大于0,其次还要大于现有的容量,当满足上面的两个条件后,进行扩容
/**
* The maximum size of array to allocate (unless necessary).
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Returns a capacity at least as large as the given minimum capacity.
* Returns the current capacity increased by the same amount + 2 if
* that suffices.
* Will not return a capacity greater than {@code MAX_ARRAY_SIZE}
* unless the given minimum capacity is greater than that.
*
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero or
* greater than Integer.MAX_VALUE
*/
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
其中扩容的关键代码是int newCapacity = (value.length << 1) + 2;
这句话的意思是 newCapacity=2*原有容量+2
并且假如这个计算出来的值小于传入参数,那么我们令newCapacity=minCapacity,如果minCapacity大于MAX_ARRAY_SIZE,我们判断一下是否越界,如果不越界的话就取minCapacity和MAX_ARRAY_SIZE中的较大值。
这里和ArrayList是不太一样的,ArrayList直接赋一个能取到的最大值,而StringBuilder则是赋当前值。
3. 总结
ArrayList和StringBuilder,主要由两个方面的区别:
1)计算新容量的方式不同
ArrayList:int newCapacity = oldCapacity + (oldCapacity >> 1);
StringBuilder: int newCapacity = (value.length << 1) + 2;
2)newCapacity大于最大容量并且Max(newCapacity,minCapacity)没有越界时的操作不同
此时有newCapacity=Max(newCapacity, minCapacity)
ArrayList:newCapacity取值为
(minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE
StringBuilder:newCapacity取值为Max(minCapacity, MAX_ARRAY_SIZE)