目录
一、获取ArrayList迭代器及迭代器源码
二、迭代器相关问题研究
1、为什么ArrayList用随机获取元素比迭代器获取元素效率高效率高
1.1 测试代码及结果
1.2 说明
2、为什么迭代器并发修改会有异常
2.1 测试代码及结果
2.2 说明
3、 迭代器并发修改特殊情况
3.1 代码及结果
3.2 说明
4、为什么调用迭代器中的remove()方法就不会报错
4.1 代码及结果
4.2 说明
一、获取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
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93//返回ArrayList的迭代器。 public Iterator<E> iterator() { //创建私有内部类,该类实现了Iterator接口 return new Itr(); } /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return 光标,默认值就是0 //lastRet本质指的是迭代器迭代到的元素下标 int lastRet = -1; // index of last element returned; -1 if no such 返回的最后一个元素的索引;-1 如果没有 //将集合实际修改次数modCount赋值给预期修改数expectedModCount int expectedModCount = modCount; //判读集合是否有元素 public boolean hasNext() { //光标不等于集合的size说明有元素 return cursor != size; } @SuppressWarnings("unchecked") public E next() { //检查预期修改数expectedModCount是否等于实际修改数modCount checkForComodification(); //光标值赋给局部变量 int i = cursor; //如果i其实也就是cursor大于等于size,则抛出异常 //因为ArrayList是用数组并且元素是连续存放的,所以数组的size的位置是没有数据的 if (i >= size) throw new NoSuchElementException(); //ArrayList成员数组赋值给局部变量数组 Object[] elementData = ArrayList.this.elementData; //如果id大于等于elementData数组的长度则抛出异常, //因为如果i大于等于说明有其他线程修改了集合 if (i >= elementData.length) throw new ConcurrentModificationException(); //游标cursor加1,也就是数组的下一个下标值 cursor = i + 1; //获取并返回元素,并且lastRet赋值为i,即本次获取元素的数组下标 return (E) elementData[lastRet = i]; } //迭代器本身的删除元素的方法 public void remove() { //lastRet默认值就是小于零的,如果获取到集合迭代器直接掉用remove方法就会报异常 if (lastRet < 0) throw new IllegalStateException(); //检查预期修改数expectedModCount是否等于实际修改数modCount checkForComodification(); try { //调用ArrayList集合的remove元素, //ArrayList集合的remove方法内部会修改modCount ArrayList.this.remove(lastRet); //lastRet赋值给cursor, //因为lastRet位置的元素删除了,调用ArrayList集合的remove方法,会把lastRet后边的元素往前移动一位 //即ArrayList.this.remove(lastRet)执行完成后,集合的lastRet位置的元素会是原来集合的lastRet+1位置的元素 //所以下次迭代元素应该继续从lastRet位置开始 cursor = lastRet; //lastRet置为-1,这样说明迭代器中的remove()方法不能连用 lastRet = -1; //将集合实际修改次数modCount重新赋值给预期修改数expectedModCount 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(); } //检查modCount 与expectedModCount是否相等 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
二、迭代器相关问题研究
1、为什么ArrayList用随机获取元素比迭代器获取元素效率高效率高
1.1 测试代码及结果
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
35public static void main(String[] args) { ArrayList<Integer> ar = new ArrayList(); for(int i=0;i<1000000;i++){ ar.add(i); } long start = System.currentTimeMillis(); //随机取出方式 for(int i=0;i<1000000;i++){ ar.get(i); } long end = System.currentTimeMillis(); System.out.println("随机取出方式时间:" + (end-start)); //顺序获取方式 start = System.currentTimeMillis(); Iterator<Integer> iterator = ar.iterator(); while (iterator.hasNext()){ iterator.next(); } end = System.currentTimeMillis(); System.out.println("顺序获取方式:" + (end-start)); } } /* 随机取出方式时间:3 顺序获取方式:6 随机取出方式时间:5 顺序获取方式:7 随机取出方式时间:3 顺序获取方式:6 随机取出方式时间:4 顺序获取方式:7 通过4次对比发现随机获取效率高 ArrayList 实现了RandomAccess接口 */
1.2 说明
随机获取元素和迭代器获取元素其实最终都是调用集合的elementData(int index)方法,但是集合的get方法中除了调用elementData方法以外只有一个下标检测的步骤,而迭代器中的next() 方法中有多个检测及赋值步骤,所以从代码层面上来说next()方法性能就比get()方法性能差
2、为什么迭代器并发修改会有异常
2.1 测试代码及结果
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
38public static void main(String[] args) { //创建集合对象 List<String> list = new ArrayList<String>(); //添加元素 list.add("hello"); list.add("Java"); list.add("PHP"); //获取迭代器 Iterator<String> it = list.iterator(); //遍历集合 while (it.hasNext()) { System.out.println(list); String s = it.next(); System.out.println(s); if(s.equals("PHP")) { list.remove("PHP"); } } } } /** * [hello, Java, PHP] * hello * [hello, Java, PHP] * Java * [hello, Java, PHP] * PHP * [hello, Java] * Exception in thread "main" java.util.ConcurrentModificationException * at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) * at java.util.ArrayList$Itr.next(ArrayList.java:851) * at com.lcg.collection.arryList.iteratorDemo.Test02.main(Test02.java:28) */
2.2 说明
从结果中可以看到异常是从checkForComodification方法中抛出的,而抛出该异常的条件是modCount 与expectedModCount不相等。从结果中看出虽然报异常了,但是我们想要删除的PHP元素也删除了。所以这个异常是从删除后的下一次集合调用next方法产生的。至于为什么会出现异常也就是modCount 与expectedModCount不相等原因是因为集合的remove方法中会对集合成员变量modCount和size进行修改,调用集合的remove方法后再调用next方法时此时的modCount 与expectedModCount就会不相等所以就会报异常。其实ArrayList集合不仅是remove方法会修改modCount值,其他比如add(),clear()等方法都会修改modCount。
3、 迭代器并发修改特殊情况
3.1 代码及结果
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
35public static void main(String[] args) { //创建集合对象 ArrayList<String> list = new ArrayList<String>(); //添加元素 list.add("hello"); list.add("Java1"); list.add("Java2"); list.add("PHP"); list.add("Java"); //获取迭代器 Iterator<String> it = list.iterator(); //遍历集合 while (it.hasNext()) { System.out.println(list); String s = it.next(); System.out.println(s); if(s.equals("PHP")) { list.remove("PHP"); } } System.out.println(list); } } /** * [hello, Java1, Java2, PHP, Java] * hello * [hello, Java1, Java2, PHP, Java] * Java1 * [hello, Java1, Java2, PHP, Java] * Java2 * [hello, Java1, Java2, PHP, Java] * PHP * [hello, Java1, Java2, Java] */
3.2 说明
这个案例中就不会报出异常,原因就是要移除的元素在集合的倒数第二个位置。出现这个特殊情况的原因是调用remove方法前,cursor值就变成size-1也就是4了,因为按照正常的迭代顺序下一次获取的元素就是最后一个元素的下标就是size-1(数组下标是从0开始的),而调用remove方法也会把size值减1,那么再调用hasNext方法因为cursor等于size,hasNext方法就会返回false,从而不会再调用next()方法所以就不会抛出异常了,这是一个特殊情况。
4、为什么调用迭代器中的remove()方法就不会报错
4.1 代码及结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public static void main(String[] args) { //创建集合对象 List<String> list = new ArrayList<String>(); //添加元素 list.add("hello"); list.add("PHP"); list.add("Java"); //获取迭代器 Iterator<String> it = list.iterator(); //遍历集合 while (it.hasNext()) { String s = it.next(); if(s.equals("hello")) { it.remove(); } } System.out.println(list); //[PHP, Java] }
4.2 说明
调用迭代器中的remove方法不会有异常的原因是迭代器中的remove方法中对expectedModCount方法进行了重新赋值即expectedModCount = modCount,所以就不会出现expectedModCount与modCount不相等的情况了。
最后
以上就是甜美睫毛膏最近收集整理的关于ArrayList迭代器分析,及相关问题分析(基于JDK8)一、获取ArrayList迭代器及迭代器源码二、迭代器相关问题研究的全部内容,更多相关ArrayList迭代器分析内容请搜索靠谱客的其他文章。
发表评论 取消回复