Ho una piccola domanda sui dettagli di implementazione che non riesco a capire ArrayList::removeIf
. Non credo di poter semplicemente dirlo così com'è senza prima alcune condizioni preliminari.
Come tale: l'implementazione è sostanzialmente una massa remove
, a differenza ArrayList::remove
. Un esempio dovrebbe rendere le cose molto più facili da capire. Diciamo che ho questo elenco:
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);
E vorrei rimuovere ogni elemento uniforme. Potrei fare:
Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
int elem = iter.next();
if (elem % 2 == 0) {
iter.remove();
}
}
Oppure :
list.removeIf(x -> x % 2 == 0);
Il risultato sarà lo stesso, ma l'implementazione è molto diversa. Dal momento che iterator
è una visione di ArrayList
, ogni volta che chiamo remove
, il sottostante ArrayList
deve essere portato in uno stato "buono", il che significa che l'array interno cambierà effettivamente. Ancora una volta, ad ogni singola chiamata di remove
, ci saranno chiamate System::arrayCopy
internamente.
Al contrario removeIf
è più intelligente. Dal momento che esegue l'iterazione internamente, può rendere le cose più ottimizzate. Il modo in cui lo fa è interessante.
Calcola innanzitutto gli indici da cui si suppone che gli elementi vengano rimossi. Questo viene fatto calcolando prima un valore minuscolo BitSet
, una matrice di long
valori in cui in ciascun indice risiede un 64 bit
valore (a long
). 64 bit
Valori multipli rendono questo un BitSet
. Per impostare un valore su un determinato offset, è innanzitutto necessario individuare l'indice nell'array e quindi impostare il bit corrispondente. Questo non è molto complicato. Supponiamo che tu voglia impostare i bit 65 e 3. Innanzitutto abbiamo bisogno di un long [] l = new long[2]
(perché siamo andati oltre i 64 bit, ma non più di 128):
|0...(60 more bits here)...000|0...(60 more bits here)...000|
Prima trovi l'indice: 65 / 64
(lo fanno effettivamente 65 >> 6
) e poi in quell'indice ( 1
) inserisci il bit necessario:
1L << 65 // this will "jump" the first 64 bits, so this will actually become 00000...10.
Stessa cosa per 3
. Di conseguenza, tale array lungo diventerà:
|0...(60 more bits here)...010|0...(60 more bits here)...1000|
Nel codice sorgente chiamano questo BitSet - deathRow
(bel nome!).
Facciamo questo even
esempio qui, dovelist = 2, 4, 6, 5, 5
- ripetono l'array e lo calcolano
deathRow
(dove siPredicate::test
trovatrue
).
deathRow = 7 (000 ... 111)
significa che gli indici = [0, 1, 2] devono essere rimossi
- ora sostituiscono gli elementi nell'array sottostante basato su quel deathRow (non entrando nei dettagli come questo è fatto)
l'array interno diventa: [5, 5, 6, 5, 5]. Fondamentalmente spostano gli elementi che dovrebbero rimanere di fronte alla matrice.
Posso finalmente portare la domanda.
A questo punto, sanno:
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
Per me, c'è un solo passo da fare qui:
void getRidOfElementsFromWToEnd() {
for(int i=w; i<end; ++i){
es[i] = null;
}
size = w;
}
Invece, questo accade:
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;
}
Ho rinominato le variabili di proposito qui.
Qual è il punto nella chiamata:
System.arraycopy(es, end, es, w, size - end);
Soprattutto size - end
, dato che end
è size
tutto il tempo - non è mai cambiato (quindi questo è sempre zero
). Questo è fondamentalmente un NO-OP qui. Quale caso d'angolo mi manca qui?
System.arraycopy(es, end, es, w, size - end)
come i dettagli di implementazione sottostanti di removeIf
? Mi sembrava quasi di leggere una risposta a qualche altra domanda in mezzo. (Leggendo il commento sopra) Sento che alla fine è finita in una domanda banale. È così?
System.arrayCopy
. Tuttavia è stato un viaggio divertente attraverso i dettagli (quel set di bit interno risulta avere la stessa idea di java.util.BitSet
)
range
...) e lo accetterò.
java.util.BitSet
. Per me, la reimplementazione delle BitSet
operazioni non sembra significativamente migliore dell'originale. L'opportunità di saltare intere parole è stata persa.