浅谈ArrayList机制
ArrayuList这个东西在代码中随处可见,因为他被称为动态数组 既可以像数组那样快速访问集合内部元素,又能够动态的增删。他的容量可以实现自增。但因其内部由数组实现,所以增删有比较消耗性能。
构造方法(Constructor)
//ArrayList的默认初始化容量
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
//定义的默认空容量的数组(final修饰,大小固定为0)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//定义不可被序列化的数组,也是ArrayList存储元素的本质
transient Object[] elementData;
// ArrayList的元素个数
private int size;
-
ArrayList的构造方法
- 无参构造
//构造一个容量为10,且元素数为空的数组列表(ArrayList) public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
这里要解释下:DEFAULTCAPACITY_EMPTY_ELEMENTDATA 不是一个空数组吗,哪来的元素为10了。这得听我娓娓道来:
在创建集合后,第一次添加元素会给内部数组的容量设置为10,DEFAULTCAPACITY_EMPTY_ELEMENTDATA相当于一个标记作用,避免了重复造轮子。
不得不说源码的魅力真是让人拍案叫绝。
// 传容量的构造方法 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的数组,然后将其引用赋给内部数组,此时ArrayList的容量为 initialCapacity,元素个数size为默认值0。
当initialCapacity = 0时,内部数组被赋予了默认空数组,因为其被final修饰了,所以此时ArrayList的容量为0,元素个数size为默认值0。
当initialCapacity < 0时,会抛出异常。
扩容机制
当我们探讨扩容时,肯定要从ArrayList的add方法走起,让我们来看看吧。
当我们 编写以下代码时:
ArrayList list = new ArrayList<>(); list.add(100); java内部方法调用顺序如下 :(注意下面源码是java17的,想看java8可以自行去看)具体实现扩容思路大同小异
public boolean add(E e) {
modCount
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
扩容分析:
当oldCapacity为0时,右移后还是0,也就是说此时扩容的大小为0+max(1,0)=1,容量从0扩展到1。那么什么时候是这种情况呢?
(1)传容量的构造方法传入的是0时,elementData被赋予的是EMPTY_ELEMENTDATA,此时数组容量为0,添加元素时,符合if的条件,会进入此扩容情况,容量从0扩展到1。
(2)传Collection元素列表的构造方法被传入空列表时,elementData被赋予的是EMPTY_ELEMENTDATA,数组容量为0,此时添加元素时,符合if的条件,会进入此扩容情况,容量从0扩展到1。
当oldCapacity大于0时,新创建的数组大小是老容量+老容量的一半,也就是老容量的1.5倍,每次扩容到原来的1.5倍。
else就剩一种情况了,也就是用默认无参构造方法创建的数组的初始扩容情况。此时的容量为0,添加一个元素时会创建一个新的数组,其大小为max(DEFAULT_CAPACITY, minCapacity)。
我们从上面的源码变量信息中可得知DEFAULT_CAPACITY是一个常量,其值为10,而minCapacity的值为(0+1),所以添加一个元素时,max(DEFAULT_CAPACITY, minCapacity)的值必为10。也就是说,当我们用默认无参构造方法创建的数组在添加元素前,ArrayList的容量为0,添加一个元素后,ArrayList的容量就变为10了。