前言
继Springsecurity后,框架的学习暂时告一段落,现在回过头来学习Java的一些源码有些许感悟。从学习编程语言开始,我们都是先学习了数组,之后才是集合。众所周知,数组在定义的时候,一般都需要定义数组的空间大小,而ArrayList集合却并不需要。之前也浑然不在意,直到后来听别人问我,为什么集合在引用的时候可以不去传入空间大小?我无言以对,那时候才知道了ArrayList的自动扩充这个概念。
ArrayList基本相关
提到ArrayList的自动扩充原理,不得不提的就是ArrayList中的构造方法与add()方法。首先我们先来看看ArrayList中定义的一些参数变量以及其中的两个构造方法。
// 默认的容量大小(常量)
private static final int DEFAULT_CAPACITY = 10;
// 定义的空数组(final修饰,大小固定为0)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 定义的默认空容量的数组(final修饰,大小固定为0)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 定义的不可被序列化的数组,实际存储元素的数组
transient Object[] elementData;
// 数组中元素的个数
private int size;
这些参数在刚开始看源码的时候显得有点折磨,完全看不懂,也是在之后慢慢看,一个个对应这找的时候才发现他们各自有各自的作用。显而易见,ArrayList也是由数组实现的,这也是它被称为动态数组的原因吧。
ArrayList的构造方法
ArrayList有三种构造方法,这里主要浅谈前两个:
- 无参的构造方法
- 根据传入的数值大小,创建指定长度的数组
- 通过传入Collection元素列表进行生成
无参构造
// 无参的构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
显而易见,当我们引用无参构造的时候,空数组elementData会被赋给集合,此时集合内的元素为0长度也为0。
根据传入的数值大小,创建指定长度的数组
/ 传容量的构造方法
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);
}
}
当initialCapacity > 0时,会在堆上new一个大小为initialCapacity的数组,然后将其引用赋给elementData,此时ArrayList的容量为initialCapacity,元素个数size为默认值0。
当initialCapacity = 0时,elementData被赋予了默认空数组,因为其被final修饰了,所以此时ArrayList的容量为0,元素个数size为默认值0。
当initialCapacity < 0时,会抛出异常。
ArrayList自动扩充
对于自动扩充,必然与add()方法脱不开干系,ArrayList有两个add方法,其中一个跟自动扩充紧密联系,那么就直接进入正题。
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
从这里还是看不出来什么的,首先rangeCheckForAdd(index)方法首先判断元素个数是否等于数组容量。
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
之后就是ensureCapacityInternal()方法了,主要自动扩容也体现在这里面。
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);
}
可以看到,当当前数组是满的,这时候就需要扩容了,而源码中调用了grow()方法,那让我们一起来看看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);
}
oldCapacity用来记录旧数组的容量,而这个newCapacity就是扩充后的容量了,相信大家都知道,ArrayList会自动扩充为原容量的1.5倍,而oldCapacity >> 1 也就是将oldCapacity右移一位,也就是缩小到原来的二分之一,这样就是1.5倍了。
后面的第一个if语句,用来检查新容量的大小是否小于最小需要容量,如果小于那就将最小容量作为数组的新容量。第二个if表示当新容量大于MAX_ARRAY_SIZE,使用hugeCapacity函数比较。最后将原数组中的元素拷贝到扩容后的数组中。
因为对源码的理解还不够,结合者查资料与debug,目前只能总结到这个地步了,以后还会继续深入研究。
ArrayList的特点
- ArrayList的底层数据结构是数组,所以查找遍历快,增删慢。因为实现了RandomAccess接口所以可以随机访问,也就是根据索引去获取元素。
- ArrayList可随着元素的增长而自动扩容,正常扩容的话,每次扩容到原来的1.5倍。
- ArrayList的线程是不安全的。
- ArrayList实现了Serializable接口,所以ArrayList可以被序列化