首页 » SEO优化 » phparraydeletekey技巧_女同伙跟我吐槽 Java 中 ArrayList 遍历时删除元素的各类姿势

phparraydeletekey技巧_女同伙跟我吐槽 Java 中 ArrayList 遍历时删除元素的各类姿势

访客 2024-11-12 0

扫一扫用手机浏览

文章目录 [+]

简介

我们在项目开拓过程中,常常会有需求须要删除ArrayList中的某个元素,而利用禁绝确的删除办法,就有可能抛出非常。
以及在口试中,常常会碰着口试官讯问ArrayList遍历时如何精确删除元素。
以是在本篇文章中,我们会对几种删除元素的办法进行测试,并对事理进行研究,希望可以帮助到大家!

ArrayList遍历时删除元素的几种姿势

首先结论如下:

phparraydeletekey技巧_女同伙跟我吐槽 Java 中 ArrayList 遍历时删除元素的各类姿势

第1种方法 - 普通for循环正序删除(结果:会漏掉元素判断)

phparraydeletekey技巧_女同伙跟我吐槽 Java 中 ArrayList 遍历时删除元素的各类姿势
(图片来自网络侵删)

第2种方法 - 普通for循环倒序删除(结果:精确删除)

第3种方法 - for-each循环删除(结果:抛出非常)

第4种方法 - Iterator遍历,利用ArrayList.remove()删除元素(结果:抛出非常)

第5种方法 - Iterator遍历,利用Iterator的remove删除元素(结果:精确删除)

下面让我们来详细探究一下缘故原由吧!

首先初始化一个数组arrayList,假设我们要删除即是3的元素。

public static void main(String[] args) { ArrayList<Integer> arrayList = new ArrayList(); arrayList.add(1); arrayList.add(2); arrayList.add(3); arrayList.add(3); arrayList.add(4); arrayList.add(5); removeWayOne(arrayList); }第1种方法 - 普通for循环正序删除(结果:会漏掉元素判断)

for (int i = 0; i < arrayList.size(); i++) {if (arrayList.get(i) == 3) {//3是要删除的元素arrayList.remove(i);//办理方案: 加一行代码i = i - 1; 删除元素后,下标减1} System.out.println("当前arrayList是"+arrayList.toString());}//原ArrayList是[1, 2, 3, 3, 4, 5]//删除后是[1, 2, 3, 4, 5]

输出结果:

当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 4, 5]当前arrayList是[1, 2, 3, 4, 5]当前arrayList是[1, 2, 3, 4, 5]

可以看到少删除了一个3,

缘故原由在于调用remove删除元素时,remove方法调用System.arraycopy()方法将后面的元素移动到前面的位置,也便是第二个3会移动到数组下标为2的位置,而不才一次循环时,i+1之后,i会为3,不会对数组下标为2这个位置进行判断,以是这种写法,在删除元素时,被删除元素a的后一个元素b会移动a的位置,而i已经加1,会忽略对元素b的判断,以是如果是连续的重复元素,会导致少删除。

办理方案

可以在删除元素后,实行i=i-1,使得下次循环时再次对该数组下标进行判断。

第2种方法 - 普通for循环倒序删除(结果:精确删除)

for (int i = arrayList.size() -1 ; i>=0; i--) { if (arrayList.get(i).equals(3)) { arrayList.remove(i); } System.out.println("当前arrayList是"+arrayList.toString());}

输出结果:

当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 4, 5]当前arrayList是[1, 2, 4, 5]当前arrayList是[1, 2, 4, 5]当前arrayList是[1, 2, 4, 5]

这种方法可以精确删除元素,由于调用remove删除元素时,remove方法调用System.arraycopy()将被删除元素a后面的元素向前移动,而不会影响元素a之前的元素,以是倒序遍历可以正常删除元素。

第3种方法 - for-each循环删除(结果:抛出非常)

public static void removeWayThree(ArrayList<Integer> arrayList) { for (Integer value : arrayList) { if (value.equals(3)) {//3是要删除的元素 arrayList.remove(value); } System.out.println("当前arrayList是"+arrayList.toString()); }}

输出结果:

当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 4, 5]Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at com.test.ArrayListTest1.removeWayThree(ArrayListTest1.java:50)at com.test.ArrayListTest1.main(ArrayListTest1.java:24)

会抛出ConcurrentModificationException非常,紧张在于for-each的底层实现是利用ArrayList.iterator的hasNext()方法和next()方法实现的,我们可以利用反编译进行验证,对包含上面的方法的类利用以下命令反编译验证

javac ArrayTest.java//天生ArrayTest.class文件javap -c ArrayListTest.class//对class文件反编译

得到removeWayThree方法的反编译代码如下:

public static void removeWayThree(java.util.ArrayList<java.lang.Integer>); Code: 0: aload_0 1: invokevirtual #12 // Method java/util/ArrayList.iterator:()Ljava/util/Iterator; 4: astore_1 5: aload_1 6: invokeinterface #13, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 调用Iterator.hasNext()方法 11: ifeq 44 14: aload_1 15: invokeinterface #14, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;调用Iterator.next()方法 20: checkcast #9 // class java/lang/Integer 23: astore_2 24: aload_2 25: iconst_3 26: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 29: invokevirtual #10 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z 32: ifeq 41 35: aload_0 36: aload_2 37: invokevirtual #15 // Method java/util/ArrayList.remove:(Ljava/lang/Object;)Z 40: pop 41: goto 5 44: return

可以很清楚得看到Iterator.hasNext()来判断是否还有下一个元素,和Iterator.next()方法来获取下一个元素。
而由于在删除元素时,remove()方法会调用fastRemove()方法,个中会对modCount+1,代表对数组进行了修正,将修正次数+1。

public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false;}private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index,numMoved); elementData[--size] = null; // clear to let GC do its work}

而当删除完元素后,进行下一次循环时,会调用下面源码中Itr.next()方法获取下一个元素,会调用checkForComodification()方法对ArrayList进行校验,判断在遍历ArrayList是否已经被修正,由于之前对modCount+1,而expectedModCount还是初始化时ArrayList.Itr工具时赋的值,以是会不相等,然后抛出ConcurrentModificationException非常。

那么有什么办法可以让expectedModCount及时更新呢?

可以看到下面Itr的源码中,在Itr.remove()方法中删除元素后会对 expectedModCount更新,以是我们在利用删除元素时利用Itr.remove()方法来删除元素就可以担保expectedModCount的更新了,详细看第5种方法。

private class Itr implements Iterator<E> { int cursor; // 游标 int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount;//期待的modCount值 public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification();//判断expectedModCount与当前的modCount是否同等 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 = modCount;//更新expectedModCount } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }第4种方法 - Iterator遍历,利用ArrayList.remove()删除元素(结果:抛出非常)

Iterator<Integer> iterator = arrayList.iterator();while (iterator.hasNext()) { Integer value = iterator.next(); if (value.equals(3)) {//3是要删除的元素 arrayList.remove(value); } System.out.println("当前arrayList是"+arrayList.toString());}

输出结果:

当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 4, 5]Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at com.test.ArrayListTest1.removeWayFour(ArrayListTest1.java:61)at com.test.ArrayListTest1.main(ArrayListTest1.java:25)

第3种方法在编译后的代码,实在是跟第4种是一样的,以是第四种写法也会抛出ConcurrentModificationException非常。
这种须要把稳的是,每次调用iterator的next()方法,会导致游标向右移动,从而达到遍历的目的。
以是在单次循环中不能多次调用next()方法,不然会导致每次循环时跳过一些元素,我在一些博客里面看到了一些缺点的写法

先调用iterator.next()获取元素,与elem进行比较,如果相等,再调用list.remove(iterator.next());来移除元素,这个时候的iterator.next()实在已经不是与elem相等的元素了,而是后一个元素了,我们可以写个demo来测试一下

ArrayList<Integer> arrayList = new ArrayList();arrayList.add(1);arrayList.add(2);arrayList.add(3);arrayList.add(4);arrayList.add(5);arrayList.add(6);arrayList.add(7);Integer elem = 3;Iterator iterator = arrayList.iterator();while (iterator.hasNext()) { System.out.println(arrayList); if(iterator.next().equals(elem)) { arrayList.remove(iterator.next()); }}

输出结果如下:

[1, 2, 3, 4, 5, 6, 7][1, 2, 3, 4, 5, 6, 7][1, 2, 3, 4, 5, 6, 7][1, 2, 3, 5, 6, 7]Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at com.test.ArrayListTest1.main(ArrayListTest1.java:29)

可以看到移除的元素实在不是3,而是3之后的元素,由于调用了两次next()方法,导致游标多移动了。
以是该当利用Integer value = iterator.next();将元素取出进行判断。

第5种方法 - Iterator遍历,利用Iterator的remove删除元素(结果:精确删除)

Iterator<Integer> iterator = arrayList.iterator();while (iterator.hasNext()) { Integer value = iterator.next(); if (value.equals(3)) {//3是须要删除的元素 iterator.remove(); }}

输出结果:

当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 3, 4, 5]当前arrayList是[1, 2, 3, 4, 5]当前arrayList是[1, 2, 4, 5]当前arrayList是[1, 2, 4, 5]当前arrayList是[1, 2, 4, 5]

可以精确删除元素。

跟第3种和第4种方法的差异在于是利用iterator.remove();来移除元素,而在remove()方法中会对iterator的expectedModCount变量进行更新,以是不才次循环调用iterator.next()方法时,expectedModCount与modCount相等,不会抛出非常。

HashMap遍历时删除元素的几种姿势

首先结论如下:

第1种方法 - for-each遍历HashMap.entrySet,利用HashMap.remove()删除(结果:抛出非常)。

第2种方法-for-each遍历HashMap.keySet,利用HashMap.remove()删除(结果:抛出非常)。

第3种方法-利用HashMap.entrySet().iterator()遍历删除(结果:精确删除)。

下面让我们来详细探究一下缘故原由吧!

HashMap的遍历删除方法与ArrayList的大同小异,只是api的调用办法不同。
首先初始化一个HashMap,我们要删除key包含"3"字符串的键值对。

HashMap<String,Integer> hashMap = new HashMap<String,Integer>();hashMap.put("key1",1);hashMap.put("key2",2);hashMap.put("key3",3);hashMap.put("key4",4);hashMap.put("key5",5);hashMap.put("key6",6);第1种方法 - for-each遍历HashMap.entrySet,利用HashMap.remove()删除(结果:抛出非常)

for (Map.Entry<String,Integer> entry: hashMap.entrySet()) { String key = entry.getKey(); if(key.contains("3")){ hashMap.remove(entry.getKey()); } System.out.println("当前HashMap是"+hashMap+" 当前entry是"+entry);}

输出结果:

当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key1=1当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key2=2当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key5=5当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key6=6当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 当前entry是key3=3Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)at java.util.HashMap$EntryIterator.next(HashMap.java:1463)at java.util.HashMap$EntryIterator.next(HashMap.java:1461)at com.test.HashMapTest.removeWayOne(HashMapTest.java:29)at com.test.HashMapTest.main(HashMapTest.java:22)第2种方法-for-each遍历HashMap.keySet,利用HashMap.remove()删除(结果:抛出非常)

Set<String> keySet = hashMap.keySet();for(String key : keySet){ if(key.contains("3")){ keySet.remove(key); } System.out.println("当前HashMap是"+hashMap+" 当前key是"+key);}

输出结果如下:

当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key1当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key2当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key5当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key6当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 当前key是key3Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)at java.util.HashMap$KeyIterator.next(HashMap.java:1453)at com.test.HashMapTest.removeWayTwo(HashMapTest.java:40)at com.test.HashMapTest.main(HashMapTest.java:23)第3种方法-利用HashMap.entrySet().iterator()遍历删除(结果:精确删除)

Iterator<Map.Entry<String, Integer>> iterator = hashMap.entrySet().iterator();while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); if(entry.getKey().contains("3")){ iterator.remove(); } System.out.println("当前HashMap是"+hashMap+" 当前entry是"+entry);}

输出结果:

当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key1=1当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key2=2当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key5=5当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key6=6当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key4=4当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 当前entry是deletekey=3

第1种方法和第2种方法抛出ConcurrentModificationException非常与上面ArrayList缺点遍历-删除方法的缘故原由同等,HashIterator也有一个expectedModCount,在遍历时获取下一个元素时,会调用next()方法,然后对

expectedModCount和modCount进行判断,不一致就抛出ConcurrentModificationException非常。

abstract class HashIterator { Node<K,V> next; // next entry to return Node<K,V> current; // current entry int expectedModCount; // for fast-fail int index; // current slot HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry do {} while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { do {} while (index < t.length && (next = t[index++]) == null); } return e; } public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; }}

PS:ConcurrentModificationException是什么?

根据ConcurrentModificationException的文档先容,一些工具不许可并发修正,当这些修正行为被检测到时,就会抛出这个非常。
(例如一些凑集不许可一个线程一边遍历时,另一个线程去修正这个凑集)。

一些凑集(例如Collection, Vector, ArrayList,LinkedList, HashSet, Hashtable, TreeMap, AbstractList, Serialized Form)的Iterator实现中,如果供应这种并发修正非常检测,那么这些Iterator可以称为是"fail-fast Iterator",意思是快速失落败迭代器,便是检测到并发修正时,直接抛出非常,而不是连续实行,等到获取到一些缺点值时在抛出非常。

非常检测紧张是通过modCount和expectedModCount两个变量来实现的,

modCount

凑集被修正的次数,一样平常是被凑集(ArrayList之类的)持有,每次调用add(),remove()方法会导致modCount+1

expectedModCount

期待的modCount,一样平常是被Iterator(ArrayList.iterator()方法返回的iterator工具)持有,一样平常在Iterator初始化时会赋初始值,在调用Iterator的remove()方法时会对expectedModCount进行更新。
(可以看看上面的ArrayList.Itr源码)

然后在Iterator调用next()遍历元素时,会调用checkForComodification()方法比较modCount和expectedModCount,不一致就抛出ConcurrentModificationException。

单线程操作Iterator不当时也会抛出ConcurrentModificationException非常。
(上面的例子便是)

WechatIMG4995.jpeg

总结

由于ArrayList和HashMap的Iterator都是上面所说的“fail-fast Iterator”,Iterator在获取下一个元素,删除元素时,都会比较expectedModCount和modCount,不一致就会抛出非常。

以是当利用Iterator遍历元素(for-each遍历底层实现也是Iterator)时,须要删除元素,一定须要利用 Iterator的remove()方法 来删除,而不是直接调用ArrayList或HashMap自身的remove()方法,否则会导致Iterator中的expectedModCount没有及时更新,之后获取下一个元素或者删除元素时,expectedModCount和modCount不一致,然后抛出ConcurrentModificationException非常。

相关文章

亿泉大数据,引领智慧时代的数据先锋

在信息化时代,大数据已成为国家战略资源,对经济社会发展具有重要意义。亿泉大数据作为国内领先的大数据企业,致力于为客户提供全方位、高...

SEO优化 2024-12-17 阅读0 评论0

介绍Q的计算奥秘,理论与方法的结合

Q,作为数学中的一个重要概念,广泛应用于各个领域。从物理学、经济学到工程学,Q的计算都具有重要意义。本文将围绕Q的计算展开,探讨其...

SEO优化 2024-12-17 阅读0 评论0