问题
如何删除list中index为偶数的元素?简单分析:首先我们得明确一点,就是不能边for循环,边删除元素,会出现越界的异常。那我们来看看怎么实现。
解决方式1:标记整理清除
套用了Old区使用的gc算法,就是将需要的元素标记,然后整理到一侧,最后删除无用元素。直接上代码:
/**
* 去掉数组的index为偶数的元素
*
* @param list list
* @return List
*/
private List<Integer> getNewList(List<Integer> list) {
int index = 0;
for (int i = 1; i < list.size(); i +=2) {
list.set(index, list.get(i));
index++;
}
return list.subList(0, index);
}
public static void main(String[] args) {
TestCase testCase = new TestCase();
List<Integer> list = Arrays.asList(0,1,2,3,4,5,6);
List<Integer> result = testCase.getNewList(list);
result.forEach(System.out::println);
}
执行结果:
1
3
5
实现起来比较简单,将index为奇数的元素挪到列表的前面位置,然后取subList即可。
解决方式2:迭代器
Iterator是JDK给集合提供用来访问和删除元素的接口,先看我写的第一版代码:
private List<Integer> getNewListV3(List<Integer> list) {
Iterator<Integer> iterator = list.listIterator();
int index = 0;
while (iterator.hasNext()) {
iterator.next();
if (index % 2 == 0) {
iterator.remove();
}
index++;
}
return list;
}
public static void main(String[] args) {
TestCase testCase = new TestCase();
List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6);
List<Integer> result = testCase.getNewListV3(list);
result.forEach(System.out::println);
}
执行结果:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.remove(AbstractList.java:161)
at java.util.AbstractList$Itr.remove(AbstractList.java:374)
at com.yu.java.TestCase.getNewListV3(TestCase.java:79)
at com.yu.java.TestCase.main(TestCase.java:22)
直接抛UnsupportedOperationException了,进源码看下,才知道默认AbstractList不支持remove元素。
/**
* {@inheritDoc}
*
* <p>This implementation always throws an
* {@code UnsupportedOperationException}.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
throw new UnsupportedOperationException();
}
那我们用arrayList试下
/**
* @param list
* @return
*/
private List<Integer> getNewListV2(ArrayList<Integer> list) {
Iterator<Integer> iterator = list.iterator();
int index = 0;
while (iterator.hasNext()) {
iterator.next();
if (index % 2 == 0) {
iterator.remove();
}
index++;
}
return list;
}
public static void main(String[] args) {
TestCase testCase = new TestCase();
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));
List<Integer> result = testCase.getNewListV2(list);
result.forEach(System.out::println);
}
执行结果:
1
3
5
顺利输出预期结果,ArrayList实现的iterator支持遍历时删除元素。我们来看下其实现原理。ArrayList的iterator内部实现较Itr,其核心类参数cursor(下一个要返回的元素的index),lastRet(上一个已返回元素的index),expectedModCount(期望修改次数 modCount是ArrayList的类变量,确保两边不会同时修改)。核心方法就是next和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;
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 = cursor + 1。
cursor = i + 1;
// lastRet = cursor 加操作之前的index,也就对应上次返回元素的index。
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//调用ArrayList本身的remove方法,使用的是System.arrayCopy()方法,
//把lastRet+1后面的元素复制到lastRet位置上。
ArrayList.this.remove(lastRet);
//remove掉一个元素后,为了保证访问的仍是原数组的下一个元素
cursor = lastRet;
// 上一个元素是被删除了,所以直接是-1,这里也表示如果要再删除的话,必须先调用next()方法。
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
源码的核心逻辑都以注释的逻辑写在代码里了。我们再根据删除偶数index的逻辑走一遍源码。
方法 | index(原数组) | cursor | lastRet |
next | 0 | 1 | 0 |
remove | 0 | 0 | -1 |
next | 1 | 1 | 0 |
next | 2 | 2 | 1 |
remove | 2 | 1 | -1 |
next | 3 | 2 | 1 |
next | 4 | 3 | 2 |
remove | 4 | 2 | -1 |
以此类推 |
通过这个过程可以看出,ArrayList的Iterator在删除元素时,是通过cursor和lastRet定位下一个要访问的元素在删除后的数组中位置来解决遍历和删除同时执行的问题。