私が理解できないことを私は小さな実装の詳細の質問があります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::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です。私はここで何のコーナーケースが行方不明ですか?
あなたはリストには、あなたが呼び出すことを特定(共通)の場合を見ている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
呼び出すshiftTailOverGap
とend
は異なる可能性が引数size
に適用されたときsubList
。
お電話の際は、同様の状況が発生しますclear()
。その場合には、実際の動作は、上でそれを呼び出すときに実行ArrayList
自体、それもコールしないように簡単ですshiftTailOverGap
方法を。以下のようなものを使用している場合にのみl.subList(a, b).clear()
、それはで終わるだろうremoveRange(a, b)
にl
すでに、呼び出し自分自身を見つけたとして、その意志今度は、shiftTailOverGap(elementData, a, b)
とb
よりも小さくすることができるがsize
。