今日、条件に応じて削除リストをトラバースするという問題が発生しました。最初は単純なforループの削除でした。この方法で書き込むことができず、安全ではないことだけを知っていましたが、なぜですか。あなたはそれについて考えましたか?リストトラバーサル削除の問題についてさらに深く掘り下げましょう!
1.いくつかの一般的なトラバーサル方法
1.通常のforループ
2.高度なforループ
3、iterator和removeIf
4、stream()
5.コピー
6.通常のforループ->逆順
第二に、ソースコード
1.通常のforループでエラーが発生する理由
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 remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
//remove会导致之后的元素往前移动,而下标不改变时就会出现bug
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
要素を削除すると、リストのサイズが変更されます。このとき、インデックスも変更されるため、トラバース時に一部の要素を見逃す可能性があります。
たとえば、最初の要素を削除した後、インデックスに基づいて2番目の要素にアクセスし続けると、削除のために次の要素が1つ前に移動したため、実際のアクセスは3番目の要素になります。
したがって、この方法は特定の要素を削除するときに使用できますが、ループ内の複数の要素を削除するのには適していません。
2.高度なforループでエラーが発生する理由
Foreachは実際に反復子を使用してトラバースしますが、トラバース中にarraylistのremoveメソッドを直接使用するとどのような問題が発生する可能性がありますか?
fastremoveおよびiteratorトラバーサルの内部コードを確認できます。
実際、これが上記の例外がスローされる原因でした。簡単に言えば、list.remove()メソッドを呼び出すと、modCountとexpectedModCountの値に一貫性がなくなり、例外が報告されます。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
//调用next时会调用checkForComodification方法检查 这两个字段
//而fastRemove里面只对modCount 进行了改变 导致抛出异常
public E next() {
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];
}
したがって、削除はトラバーサル中のforeachには適用されません。
3、java8中新方法removeIf
//内部其实就是迭代器遍历
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
イテレーターと同様に、内部実装もイテレーターです。
3、まとめ
1.メモリサイズがOOMと表示されるかどうかを考慮せずに、新しいリストをコピーする方が高速です。コレクションにオブジェクトが少ない場合に適しています。結局、追加操作のみが必要です。
2.コレクション内の要素が多すぎると、リストのコピーが少し面倒になります。イテレーターを使用するとトラバースが速くなり、小さな添え字の変更に注意を払う必要がありません。
3.パフォーマンスが考慮されていない場合は、removeIfメソッドを使用します。コードは簡潔で明確です。
4.コーナーマークの要素を削除する場合は、リバーストラバーサル方式を使用するのが最も適切です。