我是靠谱客的博主 伶俐山水,这篇文章主要介绍深入迭代器,了解并发修改异常迭代器的一些基础知识遍历ArrrayList移除一个元素迭代器源码探究,现在分享给大家,希望可以做个参考。

迭代器的一些基础知识

  1. hasNext() :此方法用来判断下一个元素,其实后面可以通过源码去理解
  2. next() :获取迭代器对象当前索引位置的元素并将索引下标移至下一个元素
  3. remove() :删除参数中指定元素

https://www.cnblogs.com/zhuyeshen/p/10956822.html

通过一个问题深入迭代器

遍历ArrrayList移除一个元素

fori遍历

正序遍历

下面代码会出现一些紧邻需要移除的重复元素没有被移除

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.ArrayList; import java.util.List; public class IteratorTest { public static void main(String[] args) { List<Character> list = new ArrayList<>(); list.add('a'); list.add('b'); list.add('c'); list.add('c'); for (int i = 0; i <list.size() ; i++) { System.out.println("当前list大小:"+list.size()); if('c'==list.get(i)){ list.remove(i); System.out.println("移除发生了,此时list大小"+list.size()); } } for (Character character : list) { System.out.println(character); } } } 当前list大小:4 当前list大小:4 当前list大小:4 移除发生了,此时list大小3 a b c Process finished with exit code 0

怎么改进呢? 可以在条件成立的时候让i–,从而保证i还在原来的位置

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.ArrayList; import java.util.List; public class IteratorTest { public static void main(String[] args) { List<Character> list = new ArrayList<>(); list.add('a'); list.add('b'); list.add('c'); list.add('c'); for (int i = 0; i <list.size() ; i++) { System.out.println("当前list大小:"+list.size()); if('c'==list.get(i)){ list.remove(i); i--; System.out.println("移除发生了,此时list大小"+list.size()); } } for (Character character : list) { System.out.println(character); } } } 当前list大小:4 当前list大小:4 当前list大小:4 移除发生了,此时list大小3 当前list大小:3 移除发生了,此时list大小2 a b Process finished with exit code 0

倒序遍历删除

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.ArrayList; import java.util.List; public class IteratorTest { public static void main(String[] args) { List<Character> list = new ArrayList<>(); list.add('a'); list.add('b'); list.add('c'); list.add('c'); for (int i = list.size()-1; i >=0; i--) { System.out.println("当前list大小:"+list.size()); if('c'==list.get(i)){ list.remove(i); System.out.println("移除发生了,此时list大小"+list.size()); } } for (Character character : list) { System.out.println(character); } } } 当前list大小:4 移除发生了,此时list大小3 当前list大小:3 移除发生了,此时list大小2 当前list大小:2 当前list大小:2 a b Process finished with exit code 0

forEach会出现异常

先让ArrayList中的泛型是引用的时候,当移除元素后面没有其他元素是不报错,但是会发现少移除了一个

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("c"); System.out.println(list.get(3)==list.get(2)); //true for (String s : list) { if(s.equals("c")){ boolean remove = list.remove(s); System.out.println("移除:"+remove); } } System.out.println(list); } } true 移除:true [a, b, c]

发现此时结果不对,有个c没有移除,这时候看源码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//ArrayList中remove(Object o)源码 public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; //移除一个后就返回true } } return false; }

快速移除的方法

复制代码
1
2
3
4
5
6
7
8
9
10
11
private void fastRemove(int index) { modCount++; 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 把末尾那个元素置为空 }
复制代码
1
2
3
4
5
6
7
8
9
//src代表原数组,srcPos原数组起始 dest 复制到的目的数组 destPos目的数组的起始位置 需要的复制长度 public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

[下面总结参考链接][https://www.cnblogs.com/ielgnahz/articles/11344406.html]

1、foreach循环遍历对象

foreach循环遍历对象的时候底层是使用迭代器进行迭代的,即该对象必须直接或者间接的实现了Iterable接口,一般以able结尾代表某种能力,实现了iterable代表给予了实现类迭代的能力。

2、foreach循环遍历数组

foreach循环遍历数组的时候将其转型成为对每一个数组元素的循环引用

还是按照上面同样的方法来进行查看

迭代器删除

迭代器对应的集合中如果元素修改,那么会导致迭代器会更新,当你调用的是迭代器的remove方法的时候,迭代器会让集合中元素移除,迭代器更新,同时迭代器会让当前cursor减1,保证还能接着正确遍历

正确的使用方式:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { if("c".equals(iterator.next())){ iterator.remove(); } } System.out.println(list); } } [a, b, d]

错误的使用方式

看下面这中情况结果虽然是对的,但是通过打断点调试我发现了当next()返回值是’c’的时候,迭代器指针(cursor)会往下走一步到达’d’所在的位置,当再调hasNext返回已经为null了.

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { if("c".equals(iterator.next())){ list.remove("c"); } } System.out.println(list); } } [a, b, d]

下面会出现删除元素没有删除干净

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("c"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { if("c".equals(iterator.next())){ System.out.println("进入这了"); list.remove("c"); } } System.out.println(list); } } 进入这了 [a, b, c]

如果下面再加一行,那么就会报并发修改异常

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println("进"); if("c".equals(iterator.next())){ System.out.println("进入这了"); list.remove("c"); } } System.out.println(list); } } 进 进 进 进入这了 进 Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at IteratorTest.main(IteratorTest.java:18) Process finished with exit code 1

当迭代器遍历到第一个c的时候,通过集合的方法移除元素,这时候迭代器会更新,但是迭代器的指针还是处于c所在位置的下一个(因为取出3后下移了一个),暂且用3位置来说吧,那么更新后迭代器2位置是元素d,但是在hasnetx返回true,然后调用next方法取出当前位置元素的时候就会报并发修改异常,在上面为什么没有报,是因为刚好hasNext返回的是false,没有调用里面的next方法.不会进里面,通过源码你会看见next方法里面会抛出并发修改异常

这里重点是调next怎么报的并发修改异常和怎么更新迭代器呢?下面来看源码

迭代器源码探究

通过下面这个报并发修改异常的代码来探究源码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println("进"); if("c".equals(iterator.next())){ System.out.println("进入这了"); list.remove("c"); } } System.out.println(list); } }

调用iterator(),走ArrayList中重写的方法

复制代码
1
2
3
4
5
public Iterator<E> iterator() { return new Itr(); }

在这里插入图片描述

会去new Itr()这个Itr是实现Iterator的类(ArrayList中的内部类,也可以称为个帮助类)

紧接着我们看这个Itr类中的方法

ArrayList中Itr的源码

首先我们看一下它的几个成员变量:

cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出

lastRet:表示上一个访问的元素的索引

expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。

modCount是AbstractList类中的一个成员变量

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.NoSuchElementException; import java.util.Objects; /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return 下标对于下一个元素返回 起始为0 int lastRet = -1; // index of last element returned; -1 if no such 下标对于最后一个元素返回,没有返回-1 int expectedModCount = modCount; //期望的数量 Itr() {} public boolean hasNext() { //判断是不是有下一个,就是判断cursor是不是等于size(集合的大小) return cursor != size; //不等于的时候返回true } @SuppressWarnings("unchecked") public E next() { //next方法 checkForComodification(); //检查,就是调用这个方法的时候里面可能包并发修改异常 int i = cursor; //i为当前cursor的值 if (i >= size) //i大于等于size说明 throw new NoSuchElementException(); //包没有元素异常 Object[] elementData = ArrayList.this.elementData; //得到这个集合的元素,同时迭代器每次通过这个去更新的迭代器中 if (i >= elementData.length) //i超过这个长度了,那么会报并发修改异常,可能多线程操作导致的 throw new ConcurrentModificationException(); cursor = i + 1; //每次操作这个值会加1 return (E) elementData[lastRet = i]; //返回的是没加1千的值 } //为什么调用迭代器remove不会包并发修改异常呢 public void remove() { if (lastRet < 0) //还没有next过一次直接调用的时候会抛出不合法状态异常 throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); //调用remove还是会让modCount加1了 cursor = lastRet; //这个步骤相当于让这个cursor往后退了一下 lastRet = -1; //同时更新她为-1,下一次如果再调迭代器remove会报异常的,除非前面有过next expectedModCount = modCount; //更新expectedModCount为新的modCount } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } //这个是上面测试代码抛异常的地方 //当调用集合的remove方法的时候你会发现会修改moCount的值,这个时候会发现modCount和expectedModCount //不相等,那么会抛并发修改异常 final void checkForComodification() { //检查modCount和期望的修改次数是否一样 if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }

走完这波源码,看看解释你应该明白了迭代器的原理,直接调用集合的remove的时候你会发现modCount改了,但是迭代器中的没改,这样会在下次调用next时候进入 checkForComodification() 方法报并发修改异常. 如果你是通过迭代器去调用的remove方法,那么该方法中会更新cursor = lastRet;相当于cursor指针后退一步,因为你移除元素了后面元素来占据这个位置了,同时更新expectedModCount这样就不会导致下次next的时候报并发修改异常了

ArrayLIst中ListItr

继承了Itr
在这里插入图片描述

  1. 使用范围不同,interator可以使用在List、Set、Queue这些接口的子类中,而ListIterator只能用在List的子类中
  2. ListIterator有add方法,set方法,而iterator没有
  3. 二者都有hasNext和Next方法,而ListIterator有hasPrevious和PreviousIndex方法可以实现逆向遍历,Iterator没有。注意:ListIterator实现逆向遍历是调用ListIterator(int index)时必须传参数
  4. ListIterator可以获得当前指针的位置,而Iterator没有
  5. ListIterator有set方法,Iterator没有

参考链接1

参考链接2

参考链接3

参考链接4

最后

以上就是伶俐山水最近收集整理的关于深入迭代器,了解并发修改异常迭代器的一些基础知识遍历ArrrayList移除一个元素迭代器源码探究的全部内容,更多相关深入迭代器,了解并发修改异常迭代器内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(73)

评论列表共有 0 条评论

立即
投稿
返回
顶部