转载自:【https://blog.csdn.net/hustwht/article/details/52181810】
【https://blog.csdn.net/superxlcr/article/details/51534428】
常见的遍历List的三种方法
- 使用普通for循环遍历
- 使用增强型for循环遍历
- 使用iterator遍历
对于线程不安全的ArrayList类,怎么实现呢?
方法1:【成功,但是删除的时候会改变list的index索引和size大小,可能会在遍历时导致一些访问越界的问题,因此不是特别推荐】
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++)
list.add(i);
// list {0, 1, 2, 3, 4}
for (int i = 0; i < list.size(); i++) {
// index and number
System.out.print(i + " " + list.get(i));
if (list.get(i) % 2 == 0) {
list.remove(list.get(i));
System.out.print(" delete");
i--; // 索引改变! 不然输出结果与预期不符
}
System.out.println();
}
}
}
方法2:【报异常,第一个会被正常删除,之后调用remove() 时,会报java.util.C_M_E异常】
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++)
list.add(i);
// list {0, 1, 2, 3, 4}
for (Integer num : list) {
// index and number
System.out.print(num);
if (num % 2 == 0) {
list.remove(num);
System.out.print(" delete");
}
System.out.println();
}
}
}
方法3:【成功,调用Iterator的remove()方法,而不是内部类的集合的remove()】
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++)
list.add(i);
// list {0, 1, 2, 3, 4}
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
// index and number
int num = it.next();
System.out.print(num);
if (num % 2 == 0) {
it.remove();
System.out.print(" delete");
}
System.out.println();
}
}
}
那么对于线程安全的 CopyOnWriteArrayList类呢?
方法1:【与ArrayList一样】
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 5; i++)
list.add(i);
// list {0, 1, 2, 3, 4}
for (int i = 0; i < list.size(); i++) {
// index and number
System.out.print(i + " " + list.get(i));
if (list.get(i) % 2 == 0) {
list.remove(list.get(i));
System.out.print(" delete");
i--; // 索引改变!
}
System.out.println();
}
}
}
方法2:【成功,与ArrayList不同,因为copy---List的设计保证他能避免 C_M_E异常的报出】
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 5; i++)
list.add(i);
// list {0, 1, 2, 3, 4}
for (Integer num : list) {
// index and number
System.out.print(num);
if (num % 2 == 0) {
list.remove(num);
System.out.print(" delete");
}
System.out.println();
}
}
}
方法3:【报错,java.lang.UnsupportedOperationException 】
A:与ArrayList不同,由于CopyOnWriteArrayList的iterator是对其List的一个“快照”,因此是不可改变的,所以无法使用iterator遍历删除。
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 5; i++)
list.add(i);
// list {0, 1, 2, 3, 4}
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
// index and number
int num = it.next();
System.out.print(num);
if (num % 2 == 0) {
it.remove();
System.out.print(" delete");
}
System.out.println();
}
}
}
综上,
当使用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 的遍历同时删除元素,再加一个实例。
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));
for(inti=0;i<list.size();i++){
list.remove(i);
}
System.out.println(list);// [b,d]输出,与预期不一样
}
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("a")) {
list.remove(i);
}
}
System.out.println(list);//[a,b,c,d]与预期不一致
}
Q: 看上面的for( ; ; )循环,以为for循环使用迭代器实现的? NoNoNo!看下面例子:
//会抛出 C_M_E异常
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","a", "b", "c", "d"));
for(String s:list){
if(s.equals("a")){
list.remove(s);
}
}
}
//输出正确,但是.next()必须在.remove()之前调用。在一个foreach循环中,编译器会使.next()在删除元素之后被调用??判断hasNext()??
//因此就会抛出ConcurrentModificationException异常,
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","a", "b", "c", "d"));
Iterator<String>
iter = list.iterator();
while(iter.hasNext()){
String s = iter.next();//要先于remove()调用
if(s.equals("a")){
iter.remove();
}
}
}