ArrayList 扩充机制
浅浅说一下ArrayList
ArrayList类又称为动态数组,同时实现了Collection和List接口,其内部数据结构由数组实现,所以可以对容器内的元素进行快速访问。但是由于内部数据结构由数组实现所以插入或删除一个元素需要移动其他元素,故不适合在插入频繁的情况下使用。
ArrayList的容量可以随着元素的增加而自动增加,因此不用担心ArrayList容量不足的问题。这也源于ArrayList的扩容机制。
通过查看源码 java11 其中的一些变量和对象如下:
//默认的容量大小(常量)
private static final int DEFAULT_CAPACITY = 10;
//定义的空的数组
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
// 定义的不可被序列化的数组,实际存储元素的数组大小为0
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
// 定义的不可被序列化的数组,实际存储元素的数组
transient Object[] elementData;
// 数组中元素的个数
private int size;
//最大数组大小
private static final int MAX_ARRAY_SIZE = 2147483639;
ArrayList中有三种构造方法
无参的构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从这里可以看出无参构造会默认将实际存储的数组赋值为空容量的数组,因为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
默认被final修饰,不可变所以此时的ArrayList数组是空的,长度固定容量为0,size大小为0。
传入一个数值创建指定大小的数组
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else {
if (initialCapacity != 0) {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
this.elementData = EMPTY_ELEMENTDATA;
}
}
从这里可以看出当initialCapacity > 0
就会,new一个数组大小为initialCapacity的大小并赋值给elementData实际存储的数组,此时ArrayList的容量为initialCapacity,元素个数size为默认值0。
然而当initialCapacity = 0
时,会将elementData赋予了默认空数组EMPTY_ELEMENTDATA
,所以此时的ArrayList数组长度固定容量为0,size大小为0。
当initialCapacity < 0
时,会抛出异常。
通过传入Collection元素列表进行生成
public ArrayList(Collection<? extends E> c) {
this.elementData = c.toArray();
if ((this.size = this.elementData.length) != 0) {
if (this.elementData.getClass() != Object[].class) {
this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);
}
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
当传入Collection元素列表后,构造方法首先会将其转化为数组,将其索引赋给elementData。
如果此数组的长度为0,会重新赋予elementData为空数组,此时ArrayList的容量是0,元素个数size为0。
如果此数组的长度大于0,会更新size的大小为其长度,即元素个数,然而此时也许有小伙伴不太理解接下来的if判断是干什么的,很简单任何一个对象都有对应的Class,就算是对象数组也不例外,这里就是将传过来的对象数组的数组转换为Object对象数组的类型,更好地存储,而且此时的ArrayList是满的。
ArrayList的扩容机制
当我们说起扩容机制就要先从add方法看起
public boolean add(E e) {
++this.modCount; //ArrayList被修改次数
this.add(e, this.elementData, this.size);
return true;
}
这里的modCount指的是ArrayList被修改次数,参数就是被添加的元素。另一个add方法,所传的值是被加元素、当前数组和当前数组的元素个数,让我们来看看这个add方法吧。
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) {
elementData = this.grow();
}
elementData[s] = e;
this.size = s + 1;
}
一进来就先进行了判断,传入的数组的个数是否等于数组当前的容量,也就是看是否够用,如果当前空间是满的,就需要扩容了,grow函数就是扩容函数了,扩容后再将被加元素加到数组中。
private Object[] grow() {
return this.grow(this.size + 1);
}
可以看到他在自己方法中有调用了另一个方法,参数是元素个数+1,也就是当前的容量+1,返回值肯定也是一个object数组。
private Object[] grow(int minCapacity) {
return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity));
}
然而在这个里面又出现了一个熟悉的方法,Arrays.copyOf前面我们聊过这个方法很相似就是复制拷贝在这个里面又有一个方法新能力。
private int newCapacity(int minCapacity) {
int oldCapacity = this.elementData.length;// 获取老容量,也就是当前容量
//新容量为老容量加上老容量向移动一位(即减半) 这里就相当于变为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(10, minCapacity);
} else if (minCapacity < 0) {
throw new OutOfMemoryError();
} else {
return minCapacity;
}
} else {
return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
}
}
从上面可以看到,方法一进来就先进行获取老容量和设置新容量,然后进行下面操作。
比较newCapacity - minCapacity <= 0
的话说明容量不够需要扩容,而后一进来先进行判断是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
我们前面说了他是别final修饰的空数组如果true表明是首次进行扩容大小为Math.max(10, minCapacity)然而minCapacity是(0+1)所以肯定为10。也就是说,当我们用默认无参构造方法创建的数组在添加元素前,ArrayList的容量为0,添加一个元素后,ArrayList的容量就变为10了。如果小于0肯定错误了,就抛出异常。
如果newCapacity - minCapacity > 0
表示不用扩容走else,判断是否成立不成立走hugeCapacity进行判断取值。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) {
throw new OutOfMemoryError();
} else {
return minCapacity > 2147483639 ? 2147483647 : 2147483639;
}
}
总结
ArrayList的特点:
1.ArrayList的底层数据结构是数组,所以查询快,增删慢。
2.ArrayList可随着元素的增长而自动扩容,正常扩容的话,每次扩容到原来的1.5倍。
3.ArrayList的线程是不安全的。
ArrayList的扩容:
1,当ArrayList的容量为0时,此时添加元素的话,需要扩容,三种构造方法扩容时略有不同:
- 无参构造,创建ArrayList后容量为0,添加第一个元素后,容量变为10,此后若需要扩容,则正常扩容。
- 传容量构造,当参数为0时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。
- 传列表构造,当列表为空时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。
2,当ArrayList的容量大于0,并且ArrayList是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的1.5倍。