摘要:要使JAVA从入门到精通,常见的遍历List的三种方法:使用普通for循环遍历,使用增强型for循环遍历,使用iterator遍历,我们掌握这些技能以后才能JAVA从入门到精通的道路上走的更远。
常见的遍历List的三种方法
· 使用普通for循环遍历
· 使用增强型for循环遍历
· 使用iterator遍历
对于线程不安全的ArrayList类,怎么实现呢?
方法1:【成功,但是删除的时候会改变list的index索引和size大小,可能会在遍历时导致一些访问越界的问题,因此不是特别推荐】
[java] view plain copy 1. public class Main { 2. public static void main(String[] args) throws Exception { 3. List<Integer> list = new ArrayList<>(); 4. for (int i = 0; i < 5; i++) 5. list.add(i); 6. // list {0, 1, 2, 3, 4} 7. for (int i = 0; i < list.size(); i++) { 8. // index and number 9. System.out.print(i + " " + list.get(i)); 10. if (list.get(i) % 2 == 0) { 11. list.remove(list.get(i)); 12. System.out.print(" delete"); 13. i--; // 索引改变! 不然输出结果与预期不符 14. } 15. System.out.println(); 16. } 17. } 18. }
方法2:【报异常,第一个会被正常删除,之后调用remove() 时,会报java.util.C_M_E异常】
[java] view plain copy 1. public class Main { 2. public static void main(String[] args) throws Exception { 3. List<Integer> list = new ArrayList<>(); 4. for (int i = 0; i < 5; i++) 5. list.add(i); 6. // list {0, 1, 2, 3, 4} 7. for (Integer num : list) { 8. // index and number 9. System.out.print(num); 10. if (num % 2 == 0) { 11. list.remove(num); 12. System.out.print(" delete"); 13. } 14. System.out.println(); 15. } 16. } 17. }
方法3:【成功,调用Iterator的remove()方法,而不是内部类的集合的remove()】
[java] view plain copy 1. public class Main { 2. public static void main(String[] args) throws Exception { 3. List<Integer> list = new ArrayList<>(); 4. for (int i = 0; i < 5; i++) 5. list.add(i); 6. // list {0, 1, 2, 3, 4} 7. Iterator<Integer> it = list.iterator(); 8. while (it.hasNext()) { 9. // index and number 10. int num = it.next(); 11. System.out.print(num); 12. if (num % 2 == 0) { 13. it.remove(); 14. System.out.print(" delete"); 15. } 16. System.out.println(); 17. } 18. } 19. }
那么对于线程安全的 CopyOnWriteArrayList类呢?
方法1:【与ArrayList一样】
[java] view plain copy 1. public class Main { 2. public static void main(String[] args) throws Exception { 3. List<Integer> list = new CopyOnWriteArrayList<>(); 4. for (int i = 0; i < 5; i++) 5. list.add(i); 6. // list {0, 1, 2, 3, 4} 7. for (int i = 0; i < list.size(); i++) { 8. // index and number 9. System.out.print(i + " " + list.get(i)); 10. if (list.get(i) % 2 == 0) { 11. list.remove(list.get(i)); 12. System.out.print(" delete"); 13. i--; // 索引改变! 14. } 15. System.out.println(); 16. } 17. } 18. }
方法2:【成功,与ArrayList不同,因为copy---List的设计保证他能避免 C_M_E异常的报出】
[java] view plain copy 1. public class Main { 2. public static void main(String[] args) throws Exception { 3. List<Integer> list = new CopyOnWriteArrayList<>(); 4. for (int i = 0; i < 5; i++) 5. list.add(i); 6. // list {0, 1, 2, 3, 4} 7. for (Integer num : list) { 8. // index and number 9. System.out.print(num); 10. if (num % 2 == 0) { 11. list.remove(num); 12. System.out.print(" delete"); 13. } 14. System.out.println(); 15. } 16. } 17. }
方法3:【报错,java.lang.UnsupportedOperationException 】
A:与ArrayList不同,由于CopyOnWriteArrayList的iterator是对其List的一个“快照”,因此是不可改变的,所以无法使用iterator遍历删除。
[java] view plain copy 1. public class Main { 2. public static void main(String[] args) throws Exception { 3. List<Integer> list = new CopyOnWriteArrayList<>(); 4. for (int i = 0; i < 5; i++) 5. list.add(i); 6. // list {0, 1, 2, 3, 4} 7. Iterator<Integer> it = list.iterator(); 8. while (it.hasNext()) { 9. // index and number 10. int num = it.next(); 11. System.out.print(num); 12. if (num % 2 == 0) { 13. it.remove(); 14. System.out.print(" delete"); 15. } 16. System.out.println(); 17. } 18. } 19. }
综上,当使用ArrayList时,我们可以使用iterator实现遍历删除;而当我们使用CopyOnWriteArrayList时,我们直接使用增强型for循环遍历删除即可,此时使用iterator遍历删除反而会出现问题。
补充 :
java中,List在遍历的时候,如果被修改了会抛出java.util.ConcurrentModificationException错误。
eg: 主线程遍历list时,子线程向list添加元素。如果想保证遍历的同时向list添加元素呢?CopyOnWriteArrayList 。
Q: CopyOnWriteArrayList 是可以保证线程安全的呢,为什么?
A: CopyOnWriteArrayList里处理写操作(包括add、remove、set等)是先将原始的数据通过JDK1.6的Arrays.copyof()来生成一份新的数组,然后在新的数据对象上进行写,写完后再将原来的引用指向到当前这个数据对象(这里应用了常识1),这样保证了每次写都是在新的对象上(因为要保证写的一致性,这里要对各种写操作要加一把锁,JDK1.6在这里用了重入锁)。
这样读操作就很快很安全,适合在多线程里使用,绝对不会发生ConcurrentModificationException 。
结论: CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。
另一个避免添加同步代码但可以避免并发修改问题的方式,在调度任务中构建一个新的列表,然后将原来指向到列表上的引用赋值给新的列表。
为了更好理解ArrayList 的遍历同时删除元素,再加一个实例。
[java] view plain copy 1. public static void main(String[] args) { 2. ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d")); 3. for(inti=0;i<list.size();i++){ 4. list.remove(i); 5. } 6. System.out.println(list);// [b,d]输出,与预期不一样 7. } [java] view plain copy 1. public static void main(String[] args) { 2. ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","a", "b", "c", "d")); 3. for (int i = 0; i < list.size(); i++) { 4. if (list.get(i).equals("a")) { 5. list.remove(i); 6. } 7. } 8. System.out.println(list);//[a,b,c,d]与预期不一致 9. }
Q: 看上面的for( ; ; )循环,以为for循环使用迭代器实现的? NoNoNo!看下面例子:
[java] view plain copy 1. //会抛出 C_M_E异常 2. public static void main(String[] args) { 3. ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","a", "b", "c", "d")); 4. for(String s:list){ 5. if(s.equals("a")){ 6. list.remove(s); 7. } 8. } 9. } [java] view plain copy 1. [java] view plain copy 1. //输出正确,但是.next()必须在.remove()之前调用。在一个foreach循环中,编译器会使.next()在删除元素之后被调用??判断hasNext()?? 2. //因此就会抛出ConcurrentModificationException异常, 3. public static void main(String[] args) { 4. ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","a", "b", "c", "d")); 5. Iterator<String> 6. iter = list.iterator(); 7. while(iter.hasNext()){ 8. String s = iter.next();//要先于remove()调用 9. if(s.equals("a")){ 10. iter.remove(); 11. } 12. } 13. }
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言JAVA频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号