集合删除的陷阱
-
一、基本原理 -
二、删除的陷阱(一) -
2.1 什么是结构性变化?如何解决 -
2.2 迭代器的原理 -
三、删除的陷阱(二)
基本原理
说到集合,最典型的就属ArrayList了,这里来说说其原理。
内部有一个数组elementData保存元素,一个整数size记录实际元素个数。
private transient Object[] elementData;
private int size;
add方法如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
add方法中,先用ensureCapacityInternal方法来判断容量是否足够,是否需要扩容,接着把添加的元素给到当前数组。ensureCapacityInternal方法如下:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity方法内部为计算最小容量,ensureExplicitCapacity方法判断是否需要扩容:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
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方法时,会判断是否需要扩容,modCount(修改次数)会+1,且是用的Arrays.copyOf实现扩容。
再来看看remove方法:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
可看到,执行删元素法remove方法时,,modCount(修改次数)也会+1,且使用的System.arraycopy实现元素的删除,index往后的元素都往前移动一位。
删除的陷阱(一)
for (Integer item : list) {
if (item <= 2) {
list.remove(item);
}
}
上面这段代码,会抛异常:
java.util.ConcurrentModificationException
因为迭代器内部会维护一些索引位置相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置就失效了。
什么是结构性变化?如何解决
结构性变化就是添加、插入和删除元素,只是修改元素内容不算结构性变化,源码中使用modCount来体现。
使用迭代器来解决,如下:
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
if (it.next() <= 2) {
it.remove();
}
}
迭代器的原理
我们来看迭代器为什么能解决以上的问题。
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
int expectedModCount = modCount;
//...
}
迭代器创建时,会把modCount赋值给expectedModCount。
重点在Iterator中的next方法,我们来看ArrayList中的迭代器next方法:
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
由上面checkForComodification可知,每次发生结构性变化的时候modCount都会增加,而每次迭代器操作的时候都会检查expectedModCount是否与modCount相同,这样就能检测出结构性变化。
这时就好知道了,迭代器中的删除,一定也会更新保持这两个变量的值相等,否则下一次调用next时就会报错,删除方法如下:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
删除的陷阱(二)
重载的选择,是我们在集合删除时,碰到的第二个陷阱。
如集合中有5个元素[1,2,3,4,5],删除前两个,可能暴露的问题代码,如下所示:
Set<Integer> set = new HashSet<>();
List<Integer> list = new ArrayList<>();
//[1,2,3,4,5]
for (int i = 1; i <= 5; i++) {
set.add(i);
list.add(i);
}
//删除前两个
for (int i = 1; i <= 2; i++) {
set.remove(i);
list.remove(i);
}
System.out.println("set:" + set); //结果为[3,4,5]
System.out.println("list:" + list); //结果为[1,3,5]
Set方法内部的删除的是:
boolean remove(Object o);
因此set方法没有疑问。
ArrayList方法内部有两个删除方法:
public E remove(int index)
public boolean remove(Object o)
很明显,调用到了以下标为参数的方法:
public E remove(int index)
改正如下,迫使其选择正确的重载方法:
list.remove((Integer) i);
本篇文章来源于微信公众号: 冥oo迹
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/10801.html