关键字段
1. private int size
ArrayList的size(所包含的元素个数)。
2. private static final Object[] EMPTY_ELEMENTDATA = {}
给空的ArrayList实例们用的,共享的,空的数组。(有参数,但是initialCapacity = 0 或者 传入的集合长度为0时用的)
3. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
给默认大小的,空的ArrayList实例们用的,共享的,空的数组(参数为空的构造函数用的) 将DEFAULTCAPACITY_EMPTY_ELEMENTDATA与EMPTY_ELEMENTDATA区别开是为了知道,在之后的操作中当第一个元素被添加进来的时候应该填充多少容量。
4. transient Object[] elementData
ArrayList内元素存放的数组。ArrayList的容量(capacity)即数组的长度。 任何elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA(一个空数组字段)的空数组,在第一个元素被加入进来时,容量会被扩充至DEFAULT_CAPACITY(10)。
构造函数
1. 传入参数指定初始容量的构造函数
用传入的参数作为初始容量
创建指定初始容量的空list
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:
- 若initialCapacity 大于0 ,初始化一个大小为initialCapacity的数组并将elementData指向该数组。
- 若initialCapacity 等于0 ,将elementData指向EMPTY_ELEMENTDATA。
- 若initialCapacity 小于0 ,抛出IllegalArgumentException异常
2. 无参构造函数
构建一个初始容量为10的空list。 (先是指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA,然后在后面用到的时候才真正的初始化elementData,一个懒加载的概念)
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3. 传入另一个集合作为参数的构造函数
构建一个包含制定集合中的元素的list,顺序为该集合构造器返回的顺序。
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;
}
}
将集合转为数组a,将数组长度赋值给size,并且判断长度的值
-
若数组长度不为0 判断集合是否为arraylist
- 集合为arraylist,elementData直接等于数组a
- 集合不为arraylist,elementData等于copy方法返回的,指定长度为size的新数组
-
若长度为0,elementData指向EMPTY_ELEMENTDATA。
In Summary
- 当未传入参数指定大小时,elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
- 当传入参数指定大小,但是传入的参数为0或者数组长度为0时,elementData指向EMPTY_ELEMENTDATA。
增加元素的方法
1. 末尾增加元素的add方法
将特定元素添加至末尾的方法
public boolean add(E e) {
// step 1
ensureCapacityInternal(size + 1); // Increments modCount!!
// step 2 注意此处先取size的值再自增!
elementData[size++] = e;
return true;
}
tips: 此处如果构造函数不是走的传入集合的那个,或者传入的集合大小为0,size都没有赋值,默认为0;
- 先是走ensureCapacityInternal这个方法,传入的参数size + 1为当前元素长度+1,即增加元素后size应该有的长度。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
先看作为参数调用的calculateCapacity(elementData, minCapacity),elementData为保存当前元素的数组,minCapacity为增加元素后size应该有的长度:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
- 如果elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,也就是刚通过无参构造函数初始化了list,此时是第一次添加数据时:返回DEFAULT_CAPACITY(10)与minCapacity(增加元素后的size应有的长度)二者之间最大值;
- 返回minCapacity(增加元素后的size应有的长度) calculateCapacity返回DEFAULT_CAPACITY或者size+1,作为参数传给ensureExplicitCapacity:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
增加元素的方法在此处对modCount的值进行了更新,涉及快速失败机制。
后面做了溢出管理?
判断了增加后的size是否比原先elementData的容量要大,如果是的话进入grow方法为数组扩容,传入的参数为size+1,即之前的元素个数+1,所需的最小容量,否则不扩容:
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赋值为原先的数组长度(elementData.length)
- newCapacity赋值为oldCapacity + oldCapacity / 2 (即原长度的1.5倍)
- 如果newCapacity 小于 传入的参数minCapacity,newCapacity = minCapacity。
- 再判断如果newCapacity小于等于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8,即2^31 - 1 - 8),如果大于MAX_ARRAY_SIZE,计算hugeCapacity:
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
先是溢出的情况,抛出oom,若minCapacity > MAX_ARRAY_SIZE,返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE;
最后Arrays.copyOf返回一个按照给定长度(newCapacity),复制了原elementData内容
addAll方法与add异曲同工,只不过size+1变成了size+集合长度
即:在newCapacity与minCapacity之间选最大的,然后再与MAX_ARRAY_SIZE做比较,若小于MAX_ARRAY_SIZE则用该值扩容,若大于则计算hugeCapacity并用该值扩容。
elementData不再等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,关于它的判断并且可能返回DEFAULT_CAPACITY的操作不会再执行。
2. 中间插入元素的方法
在list的特定位置插入特定元素。将当前位置上的元素(如果有的话)向右平移,剩下的在右边的元素也都向右(索引+1)。
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方法用以检查索引值是否越界:
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
总结
- 若ArrayList通过无参构造函数初始化,第一次增加元素时,将增加元素后的size应有的长度与DEFAULT_CAPACITY(10)做比较,大的那个作为容量。 后续操作不再考虑DEFAULT_CAPACITY,直接使用增加元素后的size应有的长度与原size的1.5倍做比较,选择大的那一方。
- 有参的构造函数如果initCapacity或者集合的长度不为0,则初始长度为initCapacity或集合的长度,即elementData的长度(oldCapacity)。增加元素时比较oldCapacity的1.5倍与增加元素后的size对比,选大的一方(与上边无参的后续操作一样)
- 涉及到overflow-conscious的先不考虑。
验证
我们来一一验证上面的结论:
1. 使用无参构造函数的情况
-
第一次增加元素,增加大小 小于等于10
List<String> list = new ArrayList<>();
list.add("apple");
// 调用无参构造函数,新增元素小于等于10,内部持有的数组扩容至10
System.out.println(getElementDataLength(list));
预期:elementData大小为10
输出结果:10
注:getElementDataLength为通过反射获取elementData长度的方法。
- 第一次增加元素,增加大小 大与10
apples为长度为12的String数组。
List<String> list1 = new ArrayList<>();
list1.addAll(Arrays.asList(apples));
// 调用无参构造函数,新增元素大于10,内部持有的数组扩容至新增元素长度
System.out.println(getElementDataLength(list1));
预期:12
输出:12
- 容量大小为10(默认)时,继续增加元素至扩充容量(增加12个元素)【该情况与有参函数的情况相同】
List<String> list = new ArrayList<>();
list.add("apple");
list.addAll(Arrays.asList(apples));
System.out.println(getElementDataLength(list));
预期:15 (同有参构造函数的操作相同)
输出:15
- 容量大小为10(默认),新增加的元素与之前元素总和超出原容量的1.5倍时,选大的(总和)【该情况与有参函数的情况相同】
List<String> list = new ArrayList<>();
// 先来五个,容量扩充至10
list.addAll(Arrays.asList("apple", "apple", "apple", "apple", "apple"));
System.out.println(getElementDataLength(list));
// 再来12个,容量扩充,在原长度的1.5倍(15)以及添加元素后总长度(17)中选择大的(17)。
list.addAll(Arrays.asList(apples));
System.out.println(getElementDataLength(list));
2. 使用传入初始大小或集合的构造函数
- 初始大小传入0
// 传入初始大小0,初始长度为0,初始长度的1.5倍为0,增加元素后长度为1,二者选大的(1)
List<String> list2 = new ArrayList<>(0);
list2.add("apple");
System.out.println(getElementDataLength(list2));
预期:1
输出:1
- 初始传入大小,element的大小为传入的值:
List<String> list2 = new ArrayList<>(21);
list2.addAll(Arrays.asList(apples));
System.out.println(getElementDataLength(list2));
预期:21
输出:21
- 后续情况与上面无参里面摆哦住的情况相同
结束
看了将近一天,不禁思考我是不是看的太过于细节了以至于失去了重点_(:з」∠)_
如有错漏,欢迎指正