删除集合中的元素,第一反应是遍历集合,比较找到相应的元素然后删除。遍历集合最容易想到的是for循环。
删除集合中为3的元素:
1 List<Integer> list = new ArrayList<Integer>();
2 for(int i = 0;i<10;i++){
3 list.add(i);
4 }
5 for(int i = 0;i<10;i++){
6 list.add(i);
7 }
8 System.out.println("删除前"+list);
9 System.out.println("size:"+list.size());
10 for(int i = 0;i<list.size();i++){
11 if(list.get(i)==3){
12 list.remove(i);
13 }
14 }
15 System.out.println("删除后"+list);
16 System.out.println("size:"+list.size());
运行结果:
删除前[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
size:20
删除后[0, 1, 2, 4, 5, 6, 7, 8, 9, 0, 1, 2, 4, 5, 6, 7, 8, 9]
size:18
看起来一点问题也没有,那么我们把集合中元素的顺序换一下。
1 List<Integer> list = new ArrayList<Integer>();
2 for(int i = 0;i<10;i++){
3 list.add(i);
4 list.add(i);
5 }
6 System.out.println("删除前"+list);
7 System.out.println("size:"+list.size());
8 for(int i = 0;i<list.size();i++){
9 if(list.get(i)==3){
10 list.remove(i);
11 }
12 }
13 System.out.println("删除后"+list);
14 System.out.println("size:"+list.size());
运行结果:
删除前[0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]
size:20
删除后[0, 0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]
size:19
问题出现了,当删除相邻两个重复元素的时候,只删除一个,这是什么原因呢?
ArrayList的底层结构是数组类型,数组的特点是删除某个元素时,后面所有元素的索引都会往前移,而此时for循环的指针是却是向下移动的。
解决方案一:使用每次删除元素的时候,将for循环指针回调一次
1 List<Integer> list = new ArrayList<Integer>();
2 for(int i = 0;i<10;i++){
3 list.add(i);
4 list.add(i);
5 }
6 System.out.println("删除前"+list);
7 System.out.println("size:"+list.size());
8 for(int i = 0;i<list.size();i++){
9 if(list.get(i)==3){
10 list.remove(i);
11 i--;
12 }
13 }
14 System.out.println("删除后"+list);
15 System.out.println("size:"+list.size());
运行结果:
删除前[0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]
size:20
删除后[0, 0, 1, 1, 2, 2, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]
size:18
解决方案二:使用迭代器
1 List<Integer> list = new ArrayList<Integer>();
2 for(int i = 0;i<10;i++){
3 list.add(i);
4 list.add(i);
5 }
6 System.out.println("删除前"+list);
7 System.out.println("size:"+list.size());
8 Iterator<Integer> it = list.iterator();
9 while(it.hasNext()){
10 if(3==it.next()){
11 it.remove();
12 }
13 }
14 System.out.println("删除后"+list);
15 System.out.println("size:"+list.size());
运行结果:
删除前[0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]
size:20
删除后[0, 0, 1, 1, 2, 2, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]
size:18
为什么使用迭代器可以实现这样的操作呢?下面将基于ArrayList的Iterator的实现分析Iterator的原理。
在ArrayList类中有个方法iterator(),此方法将返回一个iterator的实现,这里可以看出实现类叫Itr,通过其它源码可知,此类是AarryList的内部类,即ArryList的Iterator实现在ArrayList内部
1 public Iterator<E> iterator() {
2 return new Itr();
3 }
ArrayList中实现类Itr类的源码:
1 private class Itr implements Iterator<E> {
2 /**
3 * 下一个返回的位置
4 */
5 int cursor = 0;
6
7 /**
8 * 当前操作的位置
9 */
10 int lastRet = -1;
11
12 /**
13 * 类似版本号,检查List是否有更新
14 */
15 int expectedModCount = modCount;
16
17 public boolean hasNext() { // 判断是否有下一个元素
18 return cursor != size();
19 }
20
21 public E next() { // 返回下一个元素
22 checkForComodification();
23 try {
24 int i = cursor; // cursor记录的是下一个元素,所以调用next时将返回的是cursor对应的元素
25 E next = get(i); // 记录需要返回的元素
26 lastRet = i; // 记录当前元素
27 cursor = i + 1; // 记录下一个元素
28 return next;
29 } catch (IndexOutOfBoundsException e) {
30 checkForComodification();
31 throw new NoSuchElementException();
32 }
33 }
34
35 public void remove() { // 移除元素
36 if (lastRet < 0)
37 throw new IllegalStateException();
38 checkForComodification(); // 检查是否有更改,remove或者add
39
40 try {
41 AbstractList.this.remove(lastRet); // 删除当前元素
42 if (lastRet < cursor) // 删除了之后指标减1
43 cursor--;
44 lastRet = -1;
45 expectedModCount = modCount; // 保持版本号一致
46 } catch (IndexOutOfBoundsException e) {
47 throw new ConcurrentModificationException();
48 }
49 }
50
51 final void checkForComodification() { // 如果有更改则抛出ConcurrentModificationException异常
52 if (modCount != expectedModCount)
53 throw new ConcurrentModificationException();
54 }
55 }
在next()方法中可以看到,通过移动cursor游标来指向集合下一个元素。
再看remove()方法,代码的43行,删除当前元素后,游标前移了。
结论:这两种方法,基本思想都是将指针回调。