普通for循环、增强for循环、迭代器对集合的增删操作问题 您所在的位置:网站首页 小集合可以推大集合吗为什么 普通for循环、增强for循环、迭代器对集合的增删操作问题

普通for循环、增强for循环、迭代器对集合的增删操作问题

#普通for循环、增强for循环、迭代器对集合的增删操作问题| 来源: 网络整理| 查看: 265

在遍及集合过程中对集合进行增删操作最好不要做,如果非要做可以利用迭代器,并发集合,或者同步代码。

单线程模式下直接使用迭代器提供的add/remove方法就行或者首先记录下标遍历完后进行增加/删除,多线程模式下建议使用同步或并发结合。

前言:

       普通for循环遍历集合过程中进行删除,如果进行大量删除会报IndexOutOfBoundsException异常,如果少量删除可以成功删除,但是循环的次数会减少,造成结果不准确。

      增强for循环遍历过程中进行删除,会报ConcurrentModificationException异常,并发修改异常。集合遍历时进行增删操作都需要留意是否会触发ConcurrentModificationException异常

 

以下以LList集合为例 普通for循环对集合增删操作问题 增加操作:能正常添加 public static void main(String[] args) { List list = new ArrayList(); list.add("a"); list.add("baidu"); list.add("c"); list.add("d"); for (int i = 0; i < list.size(); i++) { if ("baidu".equals(list.get(i))) { list.add("com"); } } System.out.println(list); }

 

删除操作:有问题 public static void main(String[] args) { List list = new ArrayList(); list.add("a"); list.add("baidu"); list.add("c"); list.add("d"); for (int i = 0; i < list.size(); i++) { list.remove(i); } System.out.println(list); }

代码的原意思是删除所有元素,但结果如图,还剩两个元素

原因:每当删除一个元素时,集合的size方法的值都会减小1,这将直接导致集合中元素的索引重新排序,进一步说,就是剩余所有元素的索引值都减1,而for循环语句的局部变量i仍然在递增,这将导致删除操作发生跳跃。从而导致上述还剩两个元素。

应将循环中的代码修改如下

  增强for循环对集合的增删操作问题 增加操作:并发修改异常 public static void main(String[] args) { List list = new ArrayList(); list.add("a"); list.add("baidu"); list.add("c"); list.add("d"); for (String s : list) { if ("baidu".equals(s)) { list.add("com");//ConcurrentModificationException } } System.out.println(list); }

使用javap -c 命令查看class文件的字节码

由上图红框圈起的部分不难发现,foreach 循环内部实际是通过 Iterator 实现的,以上代码等同于:

public static void main(String[] args) { List list = new ArrayList(); list.add("a"); list.add("baidu"); list.add("c"); list.add("d"); for (Iterator i = list.iterator(); i.hasNext(); ) { String item = i.next(); if ("baidu".equals(item)) { list.add("com"); } } System.out.println(list); } 删除操作:并发修改异常,原理同增加操作一样 public static void main(String[] args) { List list = new ArrayList(); list.add("a"); list.add("baidu"); list.add("c"); list.add("d"); for (String s : list) { if ("baidu".equals(s)) { list.remove(s); } } System.out.println(list); }

 

迭代器对集合的增删操作问题 增加操作:因为Iterator没有add方法,此处我们使用List特有的迭代器ListIterator正常添加

public static void main(String[] args) { List list = new ArrayList(); list.add("a"); list.add("baidu"); list.add("c"); list.add("d"); ListIterator it = list.listIterator(); while(it.hasNext()){ String str = it.next(); if ("baidu".equals(str)) { it.add("com"); } } System.out.println(list); } 删除操作:正常删除 public static void main(String[] args) { List list = new ArrayList(); list.add("a"); list.add("baidu"); list.add("c"); list.add("d"); Iterator it = list.iterator(); while(it.hasNext()){ String str = it.next(); if ("baidu".equals(str)) { it.remove(); } } System.out.println(list); } 为什么迭代器对集合的增删操作不会出错

原因:迭代器内部还是利用ArrayList的添加删除函数进行操作,只不过操作只有会对相应的指针进行修改(下一个),如果进行了删除操作,集合整体长度变小,指向下一个的指针也会相应减小,所以再次访问下一个时就不会发生错误了。

源码:

terator 接口包含以下几个主要方法:

boolean hasNext(); // 检查是否有下个元素 E next(); // 获取下个元素 void remove(); // 移除当前指向的元素

ArrayList 的内部类 Itr 实现了 Iterator 接口,Itr 共有3个成员变量:

private class Itr implements Iterator { int cursor; // 下一次遍历的元素的位置 int lastRet = -1; // 前一次返回的元素的位置 int expectedModCount = modCount;

modCount 是 ArrayList 继承自 AbstractList 的一个变量。在AbstractList的源码注释中,是这样解释这个变量的:

The number of times this list has been structurally modified. Structural modifications are those that change the size of the list.

翻译成中文大意为:modCount 为 list 的结构变化次数,即 list 的元素数量变化次数。

查看 ArrayList 的源码,会发现在每次调用 add() 和 remove() 方法,都会进行 modCount++ 操作。

modCount 意为 list 的结构变化次数,而 expectedModCount 可被视为 Itr 内部记录的集合结构变化次数,该变量的作用如下。在 Itr 内部有一个 checkForComodification 方法,如下所示:

final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }

当集合的实际结构变化次数 和 Itr 记录的变化次数不相等时,则抛出 ConcurrentModificationException 异常。而在 Itr 的 next() 方法 和 remove() 中都调用了 checkForComodification 方法。

ArrayList 内部 Itr 的 remove 方法的源码:

public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); // 调用集合的remove()方法 cursor = lastRet; lastRet = -1; expectedModCount = modCount; // 更新expectedModCount } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }

实际上,调用 Itr 的 remove() 方法移除集合元素时,首先会调用 ArrayList 的 remove() 方法,再对 expectedModCount 进行更新。在下次调用 Itr.next() 方法获取下个元素时,不会出现 expectedModCount != modCount 的情况。

Iterator 为什么要检查集合的结构变化次数?

这其实是为了防止多线程并发修改集合,在一个线程遍历集合的同时,另一个线程同时增删集合元素,将无法保证数据的一致性,集合的遍历过程也将被打乱。采用 modCount 机制,在此情景下及时抛出异常,确保同一时间只会有一个线程修改或遍历集合,也即 fail-fast 策略。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有