当前位置:   article > 正文

Java常见的遍历集合场景、方法总结和注意事项_java集合遍历

java集合遍历

1、遍历List

  • for循环遍历

  1. List<String> list = new ArrayList<>();
  2. for (int i = 0; i < list.size(); i++) {
  3. String item = list.get(i);
  4. System.out.println(item);
  5. }
  • forEach遍历

  1. List<String> list = new ArrayList<>();
  2. for (String item : list) {
  3. System.out.println(item);
  4. }
  • iterator遍历

  1. List<String> list = new ArrayList<>();
  2. Iterator<String> it = list.iterator();
  3. while (it.hasNext()) {
  4. String item = it.next();
  5. System.out.println(item);
  6. }
  1. 遍历Set

        Set是无序的集合类型,其遍历方式和List基本相同,也包括for循环遍历、forEach、iterator遍历等。

  • for循环遍历

  1. Set<String> set = new HashSet<>();
  2. for (String item : set) {
  3. System.out.println(item);
  4. }
  • forEach遍历

  1. Set<String> set = new HashSet<>();
  2. for (String item : set) {
  3. System.out.println(item);
  4. }
  • iterator遍历

  1. Set<String> set = new HashSet<>();
  2. Iterator<String> it = set.iterator();
  3. while (it.hasNext()) {
  4. String item = it.next();
  5. System.out.println(item);
  6. }
  1. 遍历Map

Map是键值对映射的集合类型,其常用的遍历方法包括for循环遍历、forEach、iterator遍历等。

  • for循环遍历

  1. Map<String, String> map = new HashMap<>();
  2. for (Map.Entry<String, String> entry : map.entrySet()) {
  3. String key = entry.getKey();
  4. String value = entry.getValue();
  5. System.out.println(key + " : " + value);
  6. }
  • forEach遍历

  1. Map<String, String> map = new HashMap<>();
  2. map.forEach((key, value) -> System.out.println(key + " : " + value));
  • iterator遍历

  1. Map<String, String> map = new HashMap<>();
  2. Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
  3. while (it.hasNext()) {
  4. Map.Entry<String, String> entry = it.next();
  5. String key = entry.getKey();
  6. String value = entry.getValue();
  7. System.out.println(key + " : " + value);
  8. }

需要注意的是,在遍历时要考虑集合的线程安全性,如果存在多个线程同时访问集合,需要使用线程安全的集合或者加锁的方式进行遍历。

例如,在Java中,可以使用ConcurrentHashMap代替HashMap,或者使用CopyOnWriteArrayList代替ArrayList来实现线程安全的集合。

加锁的方式可以使用synchronized关键字或者Lock接口来实现。加锁的方式在一个线程访问共享数据时能够保证其他线程无法访问,从而保证数据的一致性。

例如,在Java中,可以使用synchronized关键字来保护共享集合的访问:

  1. List<String> list = Collections.synchronizedList(new ArrayList<String>());
  2. synchronized (list) {
  3. Iterator<String> iterator = list.iterator();
  4. while (iterator.hasNext()) {
  5. String str = iterator.next();
  6. // 针对str进行操作
  7. }
  8. }

这里使用了synchronized关键字来保护对共享集合的访问。在遍历时获取了集合的迭代器,然后使用synchronized关键字同步对集合的访问。这样,其他线程就无法访问集合,从而保证了数据的一致性。

遍历集合时不建议一边遍历一边删除的原因是:

  1. 并发修改异常:若在使用Iterator遍历集合时,一边遍历一边删除元素,就会导致遍历时集合结构发生变化,从而引发ConcurrentModificationException并发修改异常。

  2. 可能漏掉元素:当在遍历集合时,删除某个元素后,集合中的其他元素的索引会发生变化,这就可能导致某些元素被漏掉而没有被遍历到。

为了避免产生并发修改异常并确保遍历到所有元素,可以使用Iterator的remove()方法在遍历时删除元素,或者使用遍历完毕后再进行元素的删除操作。

另外,在Java中也提供了线程安全的集合类,例如ConcurrentHashMap、CopyOnWriteArrayList等,在多线程并发访问时可以保证线程安全,但是在使用时仍然需要注意遍历时的并发修改问题。

当使用Iterator遍历List时,在遍历时进行删除操作会出现并发修改异常,例如下面的示例代码:

  1. List<Integer> list = new ArrayList<>();
  2. list.add(1);
  3. list.add(2);
  4. list.add(3);
  5. for (Integer i : list) {
  6. if (i == 2) list.remove(i);
  7. System.out.println(i);
  8. }

这个代码通过for-each的方式遍历list集合,当遍历到值为2的元素时,会进行删除操作,但结果会抛出ConcurrentModificationException异常:

  1. 1
  2. Exception in thread "main" java.util.ConcurrentModificationException
  3. at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1011)
  4. at java.util.ArrayList$Itr.next(ArrayList.java:963)
  5. at com.example.demo.Main.main(Main.java:13)

为了避免这个问题,可以改用Iterator遍历并使用Iterator的remove()方法进行元素删除,例如:

  1. List<Integer> list = new ArrayList<>();
  2. list.add(1);
  3. list.add(2);
  4. list.add(3);
  5. Iterator<Integer> iterator = list.iterator();
  6. while (iterator.hasNext()) {
  7. Integer i = iterator.next();
  8. if (i == 2) iterator.remove();
  9. System.out.println(i);
  10. }

这个代码使用Iterator迭代器遍历list集合,当遍历到值为2的元素时,使用Iterator的remove()方法进行元素删除操作,避免了并发修改异常的问题,并正常输出遍历结果:

  1. 1
  2. 3

关于需要循环删除 List 中的元素,方法总结如下:

比如有以下这个 List:

public List<String> initList = Arrays.asList("张三""李四""周一""刘四""李强""李白");

怎么删除 List 中姓李的人?

1、普通 for 循环删除(不可靠)

  1. public void remove1() {
  2.     List<String> list = new ArrayList(initList);
  3.     for (int i = 0; i < list.size(); i++) {
  4.         String str = list.get(i);
  5.         if (str.startsWith("李")) {
  6.             list.remove(i);
  7.         }
  8.     }
  9.     System.out.println(list);
  10. }

输出结果:

[张三, 周一, 刘四, 李白]

WC,李白没删干净?

问题就出在 list.size(),list.size() 和 i 都是动态变化的,i 的值一直在累加,list.size() 一直在减少,所以 list 就会早早结束了循环。

所以这种方式虽然不会报错,但存在隐患,并且不容易被察觉,不建议使用。

2、普通 for 循环提取变量删除(抛异常)

把上面的示例中的 size 提出变量:

  1. public void remove2() {
  2.     List<String> list = new ArrayList(initList);
  3.     int size = list.size();
  4.     for (int i = 0; i < size; i++) {
  5.         String str = list.get(i);
  6.         if (str.startsWith("李")) {
  7.             list.remove(i);
  8.         }
  9.     }
  10.     System.out.println(list);
  11. }

输出结果:

直接下标溢出了

因为 size 变量是固定的,list 的实际大小是不断减小的,而 i 的大小是不断累加的,一旦 i >= list 的实际大小肯定就异常了。

3、普通 for 循环倒序删除(可靠)

  1. public void remove3() {
  2.     List<String> list = new ArrayList(initList);
  3.     for (int i = list.size() - 1; i > 0; i--) {
  4.         String str = list.get(i);
  5.         if (str.startsWith("李")) {
  6.             list.remove(i);
  7.         }
  8.     }
  9.     System.out.println(list);
  10. }

输出结果:

[张三, 周一, 刘四]

结果输出正常,这种删除方式就算把 list.size() 提出变量也是 OK 的,因为循环中只用到了一次。

4、增强 for 循环删除(抛异常)

  1. public void remove3() {
  2.     List<String> list = new ArrayList(initList);
  3.     for (String element : list) {
  4.         if (element.startsWith("李")) {
  5.             list.remove(element);
  6.         }
  7.     }
  8.     System.out.println(list);
  9. }

输出结果:

又抛异常了。不过这次的异常和上面的下标异常不太一样,这次是:

java.util.ConcurrentModificationException

这个是集合操作中很常见的异常之一,即并发修改异常!

其实,for(xx in xx) 就是增强的 for循环,即迭代器 Iterator 的加强实现,其内部是调用的 Iterator 的方法,为什么会报 ConcurrentModificationException 错误,我们来看下源码:

取下个元素的时候都会去判断要修改的数量(modCount)和期待修改的数量(expectedModCount)是否一致,不一致则会报错,而 ArrayList 中的 remove 方法并没有同步期待修改的数量(expectedModCount)值,所以会抛异常了。

5、迭代器循环迭代器删除(可靠)

  1. public void remove4() {
  2.     List<String> list = new ArrayList(initList);
  3.     for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
  4.         String str = iterator.next();
  5.         if (str.contains("李")) {
  6.             iterator.remove();
  7.         }
  8.     }
  9.     System.out.println(list);
  10. }

输出结果:

[张三, 周一, 刘四]

结果输出正常,这是因为迭代器中的 remove 方法将期待修改的数量(expectedModCount)值进行了同步:

所以,这种删除方法是安全的,推荐使用。

6、迭代器循环集合删除(抛异常)

  1. public void remove5() {
  2.     List<String> list = new ArrayList(initList);
  3.     for (Iterator<String> ite = list.iterator(); ite.hasNext(); ) {
  4.         String str = ite.next();
  5.         if (str.contains("李")) {
  6.             list.remove(str);
  7.         }
  8.     }
  9.     System.out.println(list);
  10. }

输出结果:

又是那个并发修改异常,这个示例虽然使用了 Iterator 循环,但删除的时候却使用了 list.remove 方法,同样是有问题的,注意了,千万别用错了。 

7、集合 forEach 方法循环删除(抛异常)

  1. public void remove6() {
  2.     List<String> list = new ArrayList(initList);
  3.     list.forEach((e) -> {
  4.         if (e.contains("李")) {
  5.             list.remove(e);
  6.         }
  7.     });
  8.     System.out.println(list);
  9. }

输出结果:

forEach 源码: 

forEach 方法的背后其实就是增强的 for 循环,底层即迭代器,所以使用 list.remove 同样抛出 ConcurrentModificationException 异常。

8、stream filter 过滤(可靠)

  1. public void remove7() {
  2.     List<String> list = new ArrayList(initList);
  3.     list = list.stream().filter(e -> !e.startsWith("李")).collect(Collectors.toList());
  4.     System.out.println(list);
  5. }

输出结果:

[张三, 周一, 刘四]

这个方法即是利用了 Stream 的筛选功能,快速过滤所需要的元素,虽然不是进行集合删除,但达到了同样的目的,这种方法要更简洁吧。

总结了 8 种循环删除 List 元素的方法:

  • 普通 for 循环删除(不可靠)

  • 普通 for 循环提取变量删除(抛异常)

  • 普通 for 循环倒序删除(可靠)

  • 增强 for 循环删除(抛异常)

  • 迭代器循环迭代器删除(可靠)

  • 迭代器循环集合删除(抛异常)

  • 集合 forEach 方法循环删除(抛异常)

  • stream filter 过滤(可靠)

参考:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/489056
推荐阅读
相关标签
  

闽ICP备14008679号