目录
扩容机制
简述一下扩容机制:
1、 先确认最少需要多少容量minCapacity可以将元素装下 (size代表当前容量,elementData.length 代表总容量)
add()方法最少需要 size + 1, addAll()方法最少需要 size + x
2、 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
即ArrayList还未分配空间,则比较 DEFAULT_CAPACITY = 10 和 minCapacity 的大小,选择最大的为最少需要容量
3、 若当前总容量elementData.length 小于 minCapactiy,则需要调用grow()扩容
4、 增长为原来的1.5倍,如果增长后还是不够,那么就增长到minCapacity。那么这样的话,此时elementData数组是满的,没有剩余空间。
如果数据太多,比MAX_ARRAY_SIZE还大了,就把空间容量扩大为Integer.MAX_VALUE
扩容机制主要由那么几个函数实现
private void ensureCapacityInternal(int minCapacity)
private void ensureExplicitCapacity(int minCapacity)
private void grow(int minCapacity)
依次讲一下
ensureCapacityInternal
区别于ensureExplicitCapacity:
这个函数是口头上说,最少需要多少容量,但是还没去查它自己到底有多少。
相当这个函数相当于一个人评估自己最少需要多少钱能满足生活,但仅仅是评估而已,他自己还不知道自己家里有多少钱。
private void ensureCapacityInternal(int minCapacity) {
//判断一下当前的ArrayList是不是由无参构造函数构造的
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
※ 这个方法出现在 add() 和addAll()方法中,在 add()方法中调用 ensureCapacityInternal(size + 1), 因为add只是添加一个元素。
而在addAll()方法中则是 ensureCapacityInternal(size + numNew)。
※ 可以把这个方法看作是 “确保Arraylist的内部存储空间至少为 minCapacity”
※ 为什么要针对无参构造函数函数来做这样一个判断呢?因为无参构造函数的minCapacity比较特殊。通过其他构造函数生成的ArrayList,它们没有所谓的默认初始容量,所以要求的最小容量就是在add()或addAll()调用ensureCapacityInternal()方法时传入的minCapacity。
而无参构造函数不同,传入的minCapactiy可能会比DEFAULT_CAPACITY更小,那么此时就多给ArrayList分配点容量,所以 minCapacity = DEFAULT_CAPACITY ;而如果传入的minCapactiy比DEFAULT_CAPACITY更大,那么就和别的ArrayList一样了,分配minCapacity的容量就行了
ensureExplicitCapacity
这个函数就是在上一个函数评估的基础上,去查自己家里有多少钱,查出来如果家里的钱不够,那么就用 grow函数去增长,增长到 minCapacity
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
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);
}
add方法
ArrayList有两个add()方法。
public boolean add(E e)
就是把元素插到尾部
public void add(int index, E element)
把元素插到指定的地方,原来的那个元素后退一格
关键是 System.arraycopy 来实现数组的移位操作,这个函数可以在做算法题的时候用,很方便。
addAll方法
public boolean addAll(Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c)
remove方法
ArrayList有两个remove()方法。
public E remove(int index)
把指定下标的元素删除
public boolean remove(Object o)
从下标0开始循环,找到o后删掉。也就是说,只删除遇到的第一个
也是利用了System.arraycopy来对数组做整体移动。
之后再将最后一个元素作 elementData[size–] =null 的操作,让垃圾回收器去清理即可。
注意remove(Object o)中,对删除元素进行判断时,如果
o= =null ,则 利用 if(o==elementData[index]) 进行判断。
o!=null , 则利用 if(o.equals(elementData[index])进行判断。
迭代器相关
ConcurrentModificationException
当执行如下代码时,会抛出ConcurrentModificationException异常
原因是主线程在遍历元素时,其他线程往集合中添加了元素。
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>(8);
list.addAll(Arrays.asList(1, 2, 3, 5, 6));
Thread thread = new Thread(() -> {
list.add(5);
list.add(4);
});
thread.start();
for (Integer integer : list) {
System.out.println(integer);
}
}
当然,这样也是不允许的
for (Integer integer : list) {
list.add(1);
System.out.println(integer);
}
剖析其源码,是因为ArrayList使用了一个modCount,当向ArrayList中添加或删除元素时,都会使modCount++,其实就类似于cas的版本号。
查看迭代器源码
/**
* 这里可以看出 迭代器是 ArrayList 的一个内部实现类 典型的迭代器模式
*/
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
// 这里将 modCount 赋值给了 expectedModCount
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
// 若修改后 modCount会变化 会与 expectedModCount 数值不相等
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
看源码可以得出获取迭代器时会将 expectedModCount 赋值为 modCount, 若在使用迭代器迭代期间修改列表则会导致两者不相等,调用next()时会进行checkForComodification检查抛异常。说明迭代时不可以进行修改操作。
modCount主要目的就是用来限制用户在迭代时修改列表,造成数据错乱
那么有没有办法在遍历一个list的时候,还向list中添加元素呢?办法是有的。就是java concurrent包中的CopyOnWriteArrayList。
具体的请看 我写的 CopyOnWriteArrayList 文章