Javaの基本-なぜArrayList、Vectorなどがループ内削除をサポートしないのですか?


JDKには、データの操作を可能にする多くのデータ構造があります。
操作データは通常、追加、削除、変更、チェック、ソート、およびその他の操作です。
実際、Vector、ArrayList、LinkedListでは、削除する方法が2つあります
。1.ループで
削除する2.直接削除する

1ベクトルを直接削除

ここに画像の説明を挿入
ここに画像の説明を挿入
直接削除するには、最初にindexOfメソッドを呼び出してターゲット要素の最初のシーケンスを取得し、次に指定したシーケンス要素を削除するメソッドを呼び出して削除します。
ここに画像の説明を挿入
指定されたシーケンス要素を削除するメソッドでは、System.arraycopyメソッドを使用して、指定された要素の後ろにあるすべての要素を実際に前方に移動します。
これは、指定されたシーケンス要素を削除する目的を達成します。

2ベクトル走査要素

循環削除について言えば、Vectorのトラバーサルに言及する必要があります。結局のところ、循環削除の基本は循環です。
では、ベクターはどのようにトラバースしますか?
Vectorには、約3つの一般的な走査方法があります。

2.1ループトラバーサル

forループトラバーサルを記述する方法は3つあります。ストリームの通常のforループと拡張されたforループおよびforeachループです。
通常のforループ:

        for(int i = 0;i < vector.size();i++){
            People people = vector.get(i);
            people.setAge(people.getAge() + 1);
        }

強化されたforループ:

for (People people : vector) {
            people.setAge(people.getAge() + 1);
        }

jdk8では、ラムダ式が追加されているため、ストリームのforeachループがあります

vector.stream().forEach(x -> x.setAge(x.getAge() + 1));

通常のforループの場合、関係するメソッドは非常に単純です。シーケンス値を指定すると、指定したシーケンスの要素を返すように配列に要求します。VectorはObjectの配列であるため、返される値はすべて参照です。メモリに保存されているオブジェクトの属性値を変更するだけです。
パッケージ化クラスと文字列の基本タイプの場合、それらは内部的に定数テーブルを維持するため、参照の代わりにgetメソッドによって特定の値が返されることがあります。したがって、通常のforループを使用して、基本パッケージングクラスと文字列ベクターを処理します。変更に失敗した可能性があります。しかし、これは私たちの考えの範囲内ではありません。

拡張されたforループの場合、その実装は通常のforループよりも複雑です。Vectorのクラスにはプライベートの内部クラスがあり
ここに画像の説明を挿入
ます。Vectorは、Iteratorインターフェイスを実装し、このインターフェイスのメソッドを実装し、拡張されたforループとiteratorループを実装できます。
したがって、厳密に言えば、拡張されたforループはイテレーターを使用して実装されます。
ストリームのforループは、スケジューリングにデフォルトのjdk8メソッドを使用し、次にイテレーターの実際の実行メソッドが呼び出されます。
ここに画像の説明を挿入
最初にコレクションのデフォルトのストリームメソッドを取得し、非並列スプリッター(スプリッター)
ここに画像の説明を挿入
取得してから、スプリッターを呼び出しますforearchメソッド:スプリッターのforEachRemainingメソッドが
ここに画像の説明を挿入
ここに画像の説明を挿入
実際に呼び出されます。
Vectorには、スプリッターインターフェイスを実装するプライベートクラスもあります。
ここに画像の説明を挿入
スプリッターメソッドには、foreach呼び出しの実装があります。
ここに画像の説明を挿入
その中でポイント検証を壊すことができます。
ここに画像の説明を挿入
コールスタックから、分析が正しいことを明確に確認できます。

2.2イテレーターループ

上記の拡張forループについて触れなかったのは、拡張forループはイテレーターを使用して実装されているため、拡張forループをここに配置した方がよいからです。
まず、Vector、ArrayList、LinkedListのいずれかを使用したので、イテレータを使用してコレクション配列をトラバースする方法を知っている必要があります。

Iterator<People> iterator = vector.iterator();
        while (iterator.hasNext()){
            People people = iterator.next();
            people.setAge(people.getAge() + 1);
        }

これは基本的に、固定モードのイテレータの書き込み方法です。
したがって、反復子トラバーサルの順序は次のとおりです。

  • イテレータオブジェクトを取得する
  • 次のオブジェクトが存在します
  • 取得してオフセット

強化されたforループもイテレーターを使用して実装されますが、上記のテンプレートコードは構文シュガーによってカプセル化されます。これは、よりシンプルで理解しやすく、使いやすいものです。
では、拡張forループはどのように実装されていますか?
イテレーターはhasNextメソッドを呼び出して次のトラバーサルを実行するかどうかを決定し、nextメソッドを使用してトラバーサル要素を取得してイテレーターをオフセットする必要があることはわかっています。
そのため、hasNextメソッドとVectorのイテレーターに実装されているnextメソッドでブレークポイントにヒットし、その呼び出しスタックを
ここに画像の説明を挿入
調べ
ここに画像の説明を挿入
ます。そのtoStringメソッドもhasNextを呼び出していることがわかりました
ここに画像の説明を挿入
ここに画像の説明を挿入
推測したとおり、拡張されたforループでhasNextメソッドが呼び出され、次のループを実行できるかどうかが判断されます。
hasNextがtrueを返す場合は、次のメソッドを呼び出して要素を取得します。
ここに画像の説明を挿入
これは構文上の糖カプセル化であるため、明示的な呼び出しはありません。

2.3あらゆる方向への横断

ベクターを後ろから前、後ろから前の2方向にトラバースします。これまでの実装はすべて前から後ろです。
イテレータを使用して、前から後ろにのみトラバースします。
通常のforループを使用すると、後ろから前に移動でき、任意の連続した配列トラバーサルを実現できます。
たとえば、10個の要素の場合、次の7個の要素をトラバースする必要がありますが、後ろから前にトラバースする必要もあります。
これは、通常のforループを使用しても実現できます。
しかし、この方法には問題があり、トラバースする場合、読み取り、追加、削除はできません。最後の7つの要素を後ろから前にトラバースする必要がある
場合
、値が8の要素を削除します。それを行うにはどうすればよいですか?
イテレータを使用してforループを拡張することは、通常のforループでは実現できません。
JDK8を使用したスト​​リーミング操作は実現できますが、プロセスも非常に煩雑で、パフォーマンスは比較的低速です。
したがって、JDKは任意の方向にトラバースする方法を提供します。

        ListIterator<People> listIterator = vector.listIterator(7);
        while (listIterator.hasPrevious()){
            People people = listIterator.previous();
            people.setAge(people.getAge() + 2);
            if(people.getAge() == 7){
                listIterator.remove();
            }
        }

ここに画像の説明を挿入
最初の7つの要素をトラバースし、操作後に値が7である要素を削除していることがわかります。
そして、それは後ろから前へトラバースされます。
それは前から後ろへ行きます:

ListIterator<People> listIterator1 = vector.listIterator();
        while (listIterator1.hasNext()){
            People people = listIterator1.next();
            people.setAge(people.getAge() + 3);
        }

ここに画像の説明を挿入

2.4ベクターのforeach

もちろん、要素をトラバースするだけの場合、Vectorはforecharメソッドも提供します
ここに画像の説明を挿入

vector.forEach(x -> x.setAge(x.getAge() + 5));

ここに画像の説明を挿入
したがって、一般に、要素をトラバースして変更するには多くの選択肢があります。
ただし、要素の数を変更する場合は、イテレータまたはストリーム操作を使用するか、これらの安全な操作をできるだけ使用して、ConcurrentModificationExceptionを回避できます。

3.ベクトル反復子の削除

前述したように、イテレータトラバーサルは次のメソッドを使用して要素とオフセットを取得します。
ただし、テストは次のメソッドで実行されます。
ここに画像の説明を挿入
ここに画像の説明を挿入
このメソッドは、modCountとexpectedModCountが一致しているかどうかを判断します。条件が一致している場合にのみ、コレクション配列が循環します。それ以外の場合、高速失敗メカニズムにより、例外がスローされ、すぐに失敗します。
expectedModCountは、イテレータオブジェクトが作成されたときに初期化され、値はmodCountに等しく
ここに画像の説明を挿入
、追加、設定、削除などのメソッドはmodCountの値を変更します。
Vector自身のメソッドはexpectedModCountを変更せず、イテレータのみがこのexpectedModCountの値を維持することに注意してください。
だから、ループに追加してもいいですか?
ここに画像の説明を挿入
ループ内では、反復子によって提供される削除メソッドを除いて、配列要素の数を変更する操作を実行することはできません。
イテレータが提供する削除メソッドが削除を実装するのはなぜですか?
主な理由は、イテレータがaddメソッドを提供しないため、Vectorは自身で実装されたaddメソッドしか追加できず、それ自体で実装されたaddメソッドはexpectedModCountの値を維持しないためです。
ループのたびに、このトラバーサルの要素を取得するために次のメソッドが呼び出され、要素の位置が次のトラバーサルにシフトされますが、チェックメソッドは次のメソッドで呼び出されます。modCountとexpectedModCountが等しくない場合、高速になります失敗しました。これがループ中に増加を実行できない理由であり、削除はイテレータによって実装された削除メソッドのみを呼び出すことができます。
expectedModCountはイテレータ自体によって維持される変数だからです。イテレータ自身の削除操作と次のサイクルの成功を確実にするために、各削除は、expectedModCountの値をmodCountの値に強制
ここに画像の説明を挿入
し、イテレータはVector自体によって実装されたremoveメソッドを呼び出します。
ここに画像の説明を挿入
これにより、配列も維持されます有効長は信頼できます。

4.ベクターは要素の削除にイテレーターを使用しません

これらを読んだ後、通常のforループを使用して削除および追加できますか?
結局のところ、通常のforループのチェックメソッドはありません。

        for(int i = 0;i < vector.size();i++){
            People people = vector.get(i);
            if (people.getAge() == 20){
                vector.add(new People(30));
            }
        }

ここに画像の説明を挿入
答えはイエスです。
削除についてはどうですか?

        for(int i = 0;i < vector.size();i++){
            People people = vector.get(i);
            if (people.getAge() == 20 || people.getAge() == 30){
                vector.remove(people);
                i--;
            }
        }

ここに画像の説明を挿入
答えはイエスですが、要素を削除した後、シーケンス値を減らす必要があることに注意してください。

5.ベクターフローは要素を削除します

4での削除に加えて、JDK8のストリーム操作を使用して要素を削除することもできます。

        vector = vector.stream().filter(people -> people.getAge() != 21).
                collect(Vector::new,Vector::add,(left,right)->left.addAll(right));

ここに画像の説明を挿入
ただし、マルチスレッドストリーム操作を考慮せずに、再構築と収集の問題を含むこの種のストリーム操作を使用すると、パフォーマンスは4の方法よりも悪くなるはずです。

ArrayListはVectorに似ています。
LinkedListの実装は、要素の数の操作において配列よりも優れている二重リンクリストです。ただし、ランダムアクセスのパフォーマンスは低くなります。

182の元の記事を公開 88のような 120,000以上を訪問

おすすめ

転載: blog.csdn.net/a18792721831/article/details/105617016