Tengo una pequeña pregunta detallada de implementación que no puedo entender ArrayList::removeIf
. No creo que pueda decirlo simplemente sin algunas condiciones previas.
Como tal: la implementación es básicamente masiva remove
, a diferencia ArrayList::remove
. Un ejemplo debería hacer las cosas mucho más fáciles de entender. Digamos que tengo esta lista:
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);
Y me gustaría eliminar cada elemento que sea par. Yo podría hacer:
Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
int elem = iter.next();
if (elem % 2 == 0) {
iter.remove();
}
}
O :
list.removeIf(x -> x % 2 == 0);
El resultado será el mismo, pero la implementación es muy diferente. Dado que iterator
es una vista de ArrayList
, cada vez que llamo remove
, el subyacente ArrayList
tiene que ser llevado a un estado "bueno", lo que significa que la matriz interna realmente cambiará. Nuevamente, en cada llamada de remove
, habrá llamadas System::arrayCopy
internas.
En el contraste removeIf
es más inteligente. Como realiza la iteración internamente, puede hacer las cosas más optimizadas. La forma en que lo hace es interesante.
Primero calcula los índices de los que se supone que deben eliminarse los elementos. Esto se hace calculando primero una pequeña BitSet
, una matriz de long
valores donde en cada índice reside un 64 bit
valor (a long
). Múltiples 64 bit
valores hacen esto a BitSet
. Para establecer un valor en un desplazamiento particular, primero necesita encontrar el índice en la matriz y luego establecer el bit correspondiente. Esto no es muy complicado. Digamos que desea establecer el bit 65 y 3. Primero necesitamos un long [] l = new long[2]
(porque fuimos más allá de 64 bits, pero no más de 128):
|0...(60 more bits here)...000|0...(60 more bits here)...000|
Primero encuentra el índice: 65 / 64
(en realidad lo hacen 65 >> 6
) y luego en ese índice ( 1
) ponga el bit necesario:
1L << 65 // this will "jump" the first 64 bits, so this will actually become 00000...10.
Lo mismo para 3
. Como tal, ese conjunto largo se convertirá en:
|0...(60 more bits here)...010|0...(60 more bits here)...1000|
En el código fuente lo llaman BitSet - deathRow
(¡buen nombre!).
Tomemos ese even
ejemplo aquí, dondelist = 2, 4, 6, 5, 5
- iteran la matriz y calculan esto
deathRow
(dondePredicate::test
estátrue
).
deathRow = 7 (000 ... 111)
los índices de significado = [0, 1, 2] deben eliminarse
- ahora reemplazan elementos en la matriz subyacente en función de ese DeathRow (sin entrar en detalles sobre cómo se hace)
matriz interna se convierte en: [5, 5, 6, 5, 5]. Básicamente, mueven los elementos que se supone que permanecen delante de la matriz.
Finalmente puedo traer la pregunta.
En este momento, ellos saben:
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
Para mí, hay un solo paso para hacer aquí:
void getRidOfElementsFromWToEnd() {
for(int i=w; i<end; ++i){
es[i] = null;
}
size = w;
}
En cambio, esto sucede:
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;
}
He cambiado el nombre de las variables a propósito aquí.
¿Cuál es el punto en llamar:
System.arraycopy(es, end, es, w, size - end);
Especialmente size - end
, ya que end
es size
todo el tiempo, nunca se cambia (por lo que esto es siempre zero
). Esto es básicamente un NO-OP aquí. ¿Qué caso de esquina me estoy perdiendo aquí?
System.arraycopy(es, end, es, w, size - end)
los detalles de implementación subyacentesremoveIf
? Casi sentí que estaba leyendo una respuesta a alguna otra pregunta en el medio. (Leyendo el comentario anterior) Siento que finalmente terminó en una pregunta trivial. ¿Es eso así?System.arrayCopy
. Sin embargo, fue un viaje divertido a través de los detalles (ese conjunto de bits internos resulta tener la misma idea quejava.util.BitSet
)range
...) y lo aceptaré.java.util.BitSet
. Para mí, la reimplementación de lasBitSet
operaciones no se ve significativamente mejor que la original. Se ha perdido la oportunidad de saltear palabras enteras.Respuestas:
Estás viendo el caso específico (común) en el que la lista, que llamas
removeIf
, es la misma queArrayList
. Solo en este caso, puede asumir queend
siempre es igual asize
.Un contraejemplo sería:
Del mismo modo,
removeAll
llamaráshiftTailOverGap
con unend
argumento que puede diferir desize
cuando se aplica asubList
.Una situación similar surge cuando llamas
clear()
. En ese caso, la operación real, realizada cuando se llama enArrayList
sí misma, es tan trivial que ni siquiera llama alshiftTailOverGap
método. Solo cuando se usa algo asíl.subList(a, b).clear()
, terminaráremoveRange(a, b)
encendidol
, lo que a su vez, como ya lo descubrió, invocaráshiftTailOverGap(elementData, a, b)
con unb
que puede ser más pequeño quesize
.fuente