で理解できない小さな実装の詳細な質問があり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
を呼び出すたびremove
に、基になるものArrayList
を「良好」な状態にする必要があります。つまり、内部配列は実際に変更されます。この場合も、の呼び出しごとにremove
、System::arrayCopy
内部で呼び出しが行われます。
対照的removeIf
にスマートです。内部で反復を行うため、物事をより最適化することができます。これを行う方法は興味深いです。
まず、要素が削除されるはずのインデックスを計算します。これは、最初に、各インデックスに値(a )が存在する値のBitSet
配列であるtinyを計算することによって行われlong
ます。複数の値はこれをaにします。特定のオフセットに値を設定するには、まず配列のインデックスを見つけてから、対応するビットを設定する必要があります。これはそれほど複雑ではありません。ビット65と3を設定するとします。最初に、次のaが必要です(64ビットを超えたが、128ビットを超えていないため)。64 bit
long
64 bit
BitSet
long [] l = new long[2]
|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::test
ですtrue
)。
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です。ここではどのコーナーケースが欠けていますか?
System.arraycopy(es, end, es, w, size - end)
の基礎となる実装の詳細としてのの使用に関する質問はありましたremoveIf
か?まるで他の質問への答えを読んでいるような気がしました。(上記のコメントを読んで)最終的には簡単な質問に終わったと思います。そうですか?
System.arrayCopy
。それにもかかわらず、詳細をたどる楽しい旅でした(その内部ビットセットはと同じ考えを持つことが判明しましたjava.util.BitSet
)
range
...)でない場合の回答を提供でき、それを受け入れます。
java.util.BitSet
。私にとって、操作の再実装は、元のBitSet
操作よりもはるかに良く見えません。単語全体をスキップする機会がありませんでした。