学数学的呢,有一个通病,好听点呢,就是想搞懂一切东西,当然,我们一般称其为:钻牛角尖,今天,我就想钻一钻ArrayList这一集合类的牛角尖
ArrayList
概述 ArrayList就是一个存放数据的集合类,可以看到API中的解释为:List接口的可调整大小的阵列实现。我们经常也能在LeetCode下刷到关于ArrayList的题目,接下来,我们就来看看这个类的真面目吧
类间关系 ArrayList类继承了AbstractList类,AbstractList又继承于AbstractCollection类,且实现了List接口,AbstractCollection类实现了Collection接口,图像形式如下:
特点:由于是List接口的子类,有List的一切特点:有序,不唯一,且由于其本身的数组结构,令它有了另一个特点:查找快,增删慢
ArrayList用于添加元素方法有add和addAll,我们主要关注add(元素)方法
add(元素)
首先,先用一个例子来说明一下该方法的作用
package demo1;
import java.util.ArrayList;
public class Demo1 {
public static void main(String[] args) {
ArrayList<Integer> arr = new ArrayList<>();
System.out.println("添加元素前:"+arr);
arr.add(10);
arr.add(50);
arr.add(30);
System.out.println("添加元素后:"+arr);
}
}
结果为:
从控制台上可以看出add()方法使ArrayList添加了元素,那么添加的原理是什么呢,我们直接上源码:
add()方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
其中element[]数组便是ArrayList元素的储存位置,size 则代表数组有效长度,即元素个数。该方法首先用函数ensureCapacityInternal(size + 1)对数组进行操作,然后将元素存入数组最后位置。因为对ensureCapacityInternal(size + 1)函数的不了解,进入其源码:
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);
}
这一段代码的逻辑如下:当上面将size + 1 传入ensureCapacityInternal()函数时,将size + 1 作为形参minCapacity,也就是最小的所需容量。
用calculateCapacity函数判断原数组是否为空,为空的话,calculateCapacity函数将会返回默认容量和size+1 中的最大值,将返回值作为最小所需容量,加入ensureExplicitCapacity函数的参数,可以看到ensureExplicitCapacity函数有两步:
第一步是将modCount++,modCount属性是AbstractList中的一个整型变量,代表对集合的操作次数,在迭代器中有所作用,是一个控制线程安全的变量
;
第二步则是判断最小容量是否大于数组长度,大于则用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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
grow()函数主要就是将旧的数组长度进行1.5倍扩容成为新数组长度,这里用了一个非常巧妙的位运算增加了运算的效率,数组长度增大之后若仍小于默认值则将默认值赋值给数组长度,若大于最大的数组长度MAX_ARRAY_SIZE(Int最大值-8),则进入hugeCapacity函数,该函数也是一个判断语句,若最小所需长度为负数(超过int的最大值)则抛出内存溢出,否则若大于MAX_ARRAY_SIZE,则返回整型最大值,若更小,则返回
MAX_ARRAY_SIZE。
长度确定后,将原数组用Arrays.copyOf()函数复制到新数组中
ok,add()方法讲述完毕,主要便是数组扩容问题: