八股文之浅谈ArrayList扩容机制篇(jdk1.8)
目录
前言:
1.ArrayList属性介绍
2.ArrayList三个构造器
3.ArrayList扩容机制
3.1 add(E e)方法
3.2 ensureCapacity方法
4.System.arraycopy() 与 Arrays.copyOf()
前言:
本文主要介绍ArrayList的扩容机制,详细说明底层在什么时候实现扩容的、是如何实现扩容的,篇幅较长,耐心看完哦~~
1.ArrayList属性介绍
默认的容量大小
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
这个是数组变量,用来表示一个数组为空,由于被final修饰,所以指向是不可以改变的。它起到一个标识的作用,用于判断数组是否为空;
/**
* 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 = {};
它们两个都是指向了空数组,但是所代表的意义不一样
- EMPTY_ELEMENTDATA 代表的意义:是表示一个数组本质为空,原生长度就是为0
- 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;
2.ArrayList三个构造器
API文档:
❀ ArrayList()
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
在无参构造器中,elementData的初始化指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA 而不是EMPTY_ELEMENTDATA,所以在这里也能看出这两个变量的代表意义是不一样的。
❀ArrayList(int initalCapacity)
/**
* 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);
}
}
这个带参比无参稍微复杂一点,参数的意义是初始化容量大小 ,
内部判断执行任务:
- 参数 > 0,会将elementData初始化为参数指定的容量大小
- 参数==0,会将elementData初始化为EMPTY_ELEMENTDATA(参数为0,说明为数组原生长度为0,为空数组,符合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;
}
}
这个构造器是用于将集合类转化为ArrayList ,例如:LinkekList转化为ArrayList
内部判断执行任务:
- 先获取到【c】的数组形式【a】,
a.length != 0
- c.getClass == ArrayList.class,【a】引用交付给elementData,
- c.getClass != ArrayList.class,Arrays.sort对a进行拷贝操作,返回拷贝后的数组引用
a.length == 0
- 说明原来的Collection 长度就为0,符合EMPTY_ELEMENT的定义,element=EMPTY_ELEMENTDATA
3.ArrayList扩容机制
3.1 add(E e)方法
/**
* 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;
}
在add方法内部,他会调用一个叫做 ensureCapacityInternal的方法,我们来看看这个方法内部细节:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
然后发现他又调用到了 calculateCapacity和ensureExplicitCapacity两个不同的方法,我们在来看内部实现代码:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
内部又再次调用grow方法,这个grow方法才是真正的扩容方法,内部代码如下:
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;
}
由此可见,简单的add方法是通过层层调用才最终实现扩容的,接下来我们具体分析一下,每一层的调用都发送了说明事情!!!大致流程如下:
简要流程概述:在进入add方法后,会先执行ensureCapacityInternal方法检查elementData数组是否还有容量,这个方法为计算这个容量去调用calculateCapacity方法,将计算后的值交给ensureExplicitCapacity来判断当前容量需不需要扩容,假如需要扩容着会调用grow 函数,然后grow函数会执行相关的扩容操作,最后执行回到,add方法中,此时不管elementData数组有没有被扩容,结果都是add都能顺利将数据添加到elementData数组中。
细节流程概述:由上面代码我们可以看到ensureCapacityInternal的第二给参数便是容量大小,而add传的便是size+1,所以这个时候,ensureCapacityInternal要做的就算,计算返回容量最大的那个值,假如此时elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,那么就把默认的DEFAULT_CAPACITY返回就好,这也是为什么有说法:无参初始化为0,而只有在add的时候才初始化为10大小的数组。假如elementData不为空了,那么就直接返回minCapacity实际上这个值等于size+1,然后来到ensureExplicitCapacity,在这个方法拿到minCapacity的值后,就拿这个值和element的实际长度来想减(minCapacity - elementData.length),假如值小于0,说明还有容量,不用扩容,直接添加元素即可,假如值大于0,说明容量不够大需要扩容,继而调用grow方法
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
由上面的代码可以看出,在grow方法内部扩容的大小:为之前的1.5倍
if (newCapacity - minCapacity < 0){
newCapacity = minCapacity;
}
//等价于
//Math.max(newCapacity,minCapacity)
之后便将newCapticity拿到最大值
if (newCapacity - MAX_ARRAY_SIZE > 0){
newCapacity = hugeCapacity(minCapacity);
}
之后又判断这个值是否大于数组的最大容量也就是整型数据的最大值-8(Integer.MAX_VALUE-8),至于为什么-8,注释中没有详细说明,但是问题不大的,在hugeCapcity方法中,会返回最合适的那个值
return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE : MAX_ARRAY_SIZE;
到这里,grow方法就结束了,然后就回到add方法中,执行添加元素操作!!!
以上就i是add方法中的扩容机制,其他的add方法也是如此,大同小异感兴趣的可以自去分析操作一下
3.2 ensureCapacity方法
比较贴心的是,官方提供了一个方法来给我们手动扩容(扩容流程参考上面介绍)
/**
* 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);
}
}
所以为了避免扩容高频发生,在我们事先指定大小的时候,可以提前扩容到指定大小,然后进行添加操作,这样就导致扩容只发生了一次,性能得到了提升
4.System.arraycopy() 与 Arrays.copyOf()
System.arraycopy()
* @param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
* @exception IndexOutOfBoundsException if copying would cause
* access of data outside array bounds.
* @exception ArrayStoreException if an element in the <code>src</code>
* array could not be stored into the <code>dest</code> array
* because of a type mismatch.
* @exception NullPointerException if either <code>src</code> or
* <code>dest</code> is <code>null</code>.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
Arrays.copyOf()
@param original the array to be copied
* @param newLength the length of the copy to be returned
* @return a copy of the original array, truncated or padded with zeros
* to obtain the specified length
public static <T> T[] copyOf(T[] original, int newLength)
两个方法在ArrayList出现都挺频繁的,前者是系统调用的拷贝,后者是用户代码调用的拷贝,
前者将源数据拷贝到指定数组中,可以指定源数组开始拷贝为位置和目标数组开始拷贝位置和拷贝长度,后者将拷贝original数组中指定长度的内容到一个数组中,将这个数组返回
以上是个人对ArrayList扩容机制的拙见
感谢阅读owo
还没有评论,来说两句吧...