一, list 集合正确删除
list 集合删除不要使用增强 for, 建议使用 for(int i;;)这种方法, 注意这种方法删除集合元素会导致索引前移导致遍历问题
例如:
- private static void delFor() {
- List<String> blist = new ArrayList<>();
- blist.add("a");
- blist.add("b");
- blist.add("c");
- blist.add("d");
- blist.add("e");
- blist.add("f");
- for (int i = 0; i <blist.size(); i++) {
- if(blist.get(i).equals("b")) {
- blist.remove(blist.get(i));
- // 这里输出被删除的元素有问题, 主要是索引改变, 删除本身没问题
- System.out.println("删除的元素是:" + blist.get(i));
- }
- }
- System.out.println(blist);
- }
二, foreach 删除
增强 for 遍历等效于使用迭代器, 在遍历中修改元素数目会报 ConcurrentModificationException
- private static void delNormal() {
- List<String> alist = new ArrayList<>();
- alist.add("1");
- alist.add("2");
- alist.add("3");
- for (String item : alist) {
- if (item.equals("2")) { // 删除 2 不报错
- alist.remove(item);
- System.out.println("被删除的元素"+item);
- }
- }
- System.out.println("遍历删除后的集合" + " " + alist);
- }
结果: 被删除的元素 2
遍历删除后的集合 [1, 3]
上面结论大家肯定知道, 但是看这段代码, 删除成功了, 并没有报错, 结论不对吗?
再看一段代码:
- private static void del() {
- List<String> blist = new ArrayList<>();
- blist.add("a");
- blist.add("b");
- blist.add("c");
- blist.add("d");
- blist.add("e");
- blist.add("f");
- for (String item : blist) {
- if(item.equals("b")) {
- blist.remove(item);
- System.out.println("删除的元素是:"+ item );
- }
- }
- System.out.println("删除后的集合为:" +blist);
- }
这段代码运行直接报错, 是上面描述的异常
- private static void delForIterator() {
- List<String> blist = new ArrayList<>();
- blist.add("a");
- blist.add("b");
- blist.add("c");
- blist.add("d");
- blist.add("e");
- blist.add("f");
- Iterator it = blist.iterator();
- while(it.hasNext()) {
- String item = (String) it.next();
- if(item.equals("b")) {
- it.remove();
- System.out.println("删除的元素是:"+ item );
- }
- }
- System.out.println("删除后的集合为:" +blist);
- }
正确删除, 没问题
我们看下迭代删除的源码:
- private class Itr implements Iterator<E> {
- int cursor; // / 将要访问的元素的索引
- int lastRet = -1; // 上一个访问元素的索引
- int expectedModCount = modCount;//expectedModCount 为预期修改值, 初始化等于 modCount(AbstractList 类中的一个成员变量)
- public boolean hasNext() {
- return cursor != size;
- }
- @SuppressWarnings("unchecked")
- public E next() {
- // 每次调用 next()需要 check
- checkForComodification();
- int i = cursor;
- if (i>= size)
- throw new NoSuchElementException();
- Object[] elementData = ArrayList.this.elementData;
- if (i>= elementData.length)
- throw new ConcurrentModificationException();
- cursor = i + 1;
- return (E) elementData[lastRet = i];
- }
- public void remove() {
- if (lastRet <0)
- throw new IllegalStateException();
- checkForComodification();
- try {
- ArrayList.this.remove(lastRet);
- cursor = lastRet;
- lastRet = -1;
- // 重新给 expectedModCount 值相等
- expectedModCount = modCount;
- } catch (IndexOutOfBoundsException ex) {
- throw new ConcurrentModificationException();
- }
- }
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }
- }
在获取一个 Iterator 对象时, 会初始化成员变量
- cursor(0)
- lastRet(-1)
- expectedModCount(ModCount = 初始集合长度).
对于第一段代码 (增强 for 底层还是调用迭代器), 不报错是因为在删除 2 以后, 调用 hasNext() 方法, cursor 值移动至 2,size 此时变成 2, 相等, 跳出循环, 所以没有报错, 这仅仅是个巧合而已.
增加 for 删除报错的主要原因是每次调用 next()方法, 都会检查 expectedModCount 和 ModCount 值是否相等, 当我们删除元素后, ModCount 会改变与 expectedModCount 值不同, 引起报错.
使用 iterator 删除时, 看上面源码, 会再次赋值它们相等, 所以不会报错.
三, 多线程情况下的集合删除
使用迭代器的 iterator.remove()在单线程下是不会报错的, 但是在多线程情况下, 一个线程修改了集合的 modCount 导致另外一个线程迭代时 modCount 与该迭代器的 expectedModCount 不相等, 这也会报异常.
- public class RemoveListForThreads implements Runnable {
- static List<String> alist = new ArrayList<>();
- public static void main(String[] args) {
- RemoveListForThreads s = new RemoveListForThreads();
- alist.add("a");
- alist.add("b");
- alist.add("c");
- alist.add("d");
- BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
- ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1,
- TimeUnit.SECONDS, workQueue);
- for (int i = 0; i <5; i++) {
- executor.execute(s);
- }
- executor.shutdown();
- }
- @Override
- public synchronized void run() {
- Iterator<String> iterator = alist.iterator();
- while (iterator.hasNext()) {
- String s = iterator.next();
- if (s.equals("c") && Thread.currentThread().getName().equals("pool-1-thread-1")) {
- iterator.remove();
- System.out.println(Thread.currentThread().getName() + " " + s);
- }
- System.out.println(Thread.currentThread().getName() + " " + s);
- }
- System.out.println(alist);
- }
- }
上面这段代码创建了一个线程池, 开启了五个线程, 在 run 方法中遍历并删除 "b" 元素, 如果该方法不加同步锁 sychronized, 也会抛出异常
来源: https://juejin.im/post/5c8779b56fb9a049a571bf0e