removeIfの実装の詳細

ユージン:

私が理解できないことを私は小さな実装の詳細の質問がありますArrayList::removeIf私は単にそれを、それは最初のいくつかの前提条件なしで道を置くことができるとは思いません。

そのように:実装は基本的にバルク removeとは異なり、ArrayList::remove例を理解する事が非常に簡単にする必要があります。のは、私はこのリストを持っているとしましょう:

List<Integer> list = new ArrayList<>(); // 2, 4, 6, 5, 5
list.add(2);
list.add(4);
list.add(6);
list.add(5);
list.add(5); 

そして、私もあるすべての要素を削除したいと思います。私はそれをできた:

Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
    int elem = iter.next();
    if (elem % 2 == 0) {
         iter.remove();
    }
}

または

list.removeIf(x -> x % 2 == 0);

結果は同じになりますが、実装は非常に異なっています。以来iteratorの図でありArrayList、毎回のIコールremove、根本的には、ArrayList内側の配列は、実際に変わることを意味し、「良い」状態にすることがあります。ここでも、のすべての単一の呼び出しでremove、呼び出しがあるでしょうSystem::arrayCopy内部。

対照的にremoveIf、よりスマートです。それは内部的に反復しないので、それは物事をより最適化することができます。それがこれを行う方法が面白いです。

これは、最初の要素がから除去されることになっているインデックスを計算します。これは、小さな計算最初によって行われるBitSetのは、配列longの各インデックスに、存在する値64 bit(値long)。複数の64 bit値は、このAを作りますBitSet特定のオフセット値を設定するには、最初の必要性は、配列のインデックスを検索し、対応するビットを設定します。これは非常に複雑ではありません。あなたが設定したいとしましょう、我々が必要とする65とまずビットlong [] l = new long[2](我々はより128より64ビットを超えて行きましたが、ありませんので)。

|0...(60 more bits here)...000|0...(60 more bits here)...000|

あなた最初のインデックスを見つける:65 / 64(彼らは実際に行う65 >> 6そのインデックスに当時)とは、( 1必要なビットを置きます:

1L << 65 // this will "jump" the first 64 bits, so this will actually become 00000...10. 

同じこと3そのように長い列がなること:

|0...(60 more bits here)...010|0...(60 more bits here)...1000|

ソースコードでは、彼らはこのたBitSetを呼び出す- deathRow素敵な名前!)。


レッツは、その取るevenここで、どこの例をlist = 2, 4, 6, 5, 5

  • 彼らは、配列を反復処理し、これを計算するdeathRow(ここPredicate::testtrue)。

deathRow = 7(000 ... 111)

インデックスを意味= [0、1、2]は除去されます

  • 彼らは今deathRowが(これがどのように行われるか詳細に行かない)ことに基づき、基本となる配列の要素を置き換えます

内部配列は次のようになる[5、5、6、5、5]。基本的に彼らは、アレイの前にとどまることになっている要素を移動します。


私は最終的に問題を持ち込むことができます。

この時点で、彼らは知っています:

 w   ->  number of elements that have to remain in the list (2)
 es  ->  the array itself ([5, 5, 6, 5, 5])
 end ->  equal to size, never changed

私には、ここで行うには、単一のステップがあります:

void getRidOfElementsFromWToEnd() {
    for(int i=w; i<end; ++i){
       es[i] = null;
    }
    size = w;
}

代わりに、この処理が行われます。

private void shiftTailOverGap(Object[] es, int w, int end) {
    System.arraycopy(es, end, es, w, size - end);
    for (int to = size, i = (size -= end - w); i < to; i++)
        es[i] = null;
}

私はここでの目的に変数の名前を変更しました。

呼び出し元のポイントは何ですか。

 System.arraycopy(es, end, es, w, size - end);

特にsize - end、以降はend ある sizeすべての時間-それは変更されることはありません(これは常にあるのでzero)。これは基本的にここにNO-OPです。私はここで何のコーナーケースが行方不明ですか?

ホルガー:

あなたはリストには、あなたが呼び出すことを特定(共通)の場合を見ているremoveIfのと同じですArrayListこの場合にのみ、あなたはそれを想定することができend、常に同じですsize

反例は次のようになります。

ArrayList<Integer> l = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 7));
l.subList(2, 5).removeIf(i -> i%2 == 1);

同様に、removeAll呼び出すshiftTailOverGapendは異なる可能性が引数sizeに適用されたときsubList

お電話の際は、同様の状況が発生しますclear()その場合には、実際の動作は、上でそれを呼び出すときに実行ArrayList自体、それもコールしないように簡単ですshiftTailOverGap方法を。以下のようなものを使用している場合にのみl.subList(a, b).clear()、それはで終わるだろうremoveRange(a, b)lすでに、呼び出し自分自身を見つけたとして、その意志今度は、shiftTailOverGap(elementData, a, b)bよりも小さくすることができるがsize

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=6271&siteId=1