Ich habe eine kleine Implementierungsdetailfrage, die ich nicht verstehe ArrayList::removeIf
. Ich glaube nicht, dass ich es einfach so ausdrücken kann, wie es ist, ohne vorher einige Voraussetzungen zu haben.
Als solches: Die Implementierung ist im Gegensatz zu einer Masse . Ein Beispiel soll das Verständnis erleichtern. Angenommen, ich habe diese Liste: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);
Und ich möchte jedes Element entfernen, das gerade ist. Ich könnte:
Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
int elem = iter.next();
if (elem % 2 == 0) {
iter.remove();
}
}
Oder :
list.removeIf(x -> x % 2 == 0);
Das Ergebnis wird das gleiche sein, aber die Implementierung ist sehr unterschiedlich. Da dies iterator
eine Ansicht von ist ArrayList
, muss jedes Mal, wenn ich aufrufe remove
, der Basiswert ArrayList
in einen "guten" Zustand gebracht werden, was bedeutet, dass sich das innere Array tatsächlich ändert. Auch bei jedem einzelnen Anruf von remove
werden System::arrayCopy
interne Anrufe getätigt.
Der Kontrast removeIf
ist schlauer. Da die Iteration intern durchgeführt wird, können die Dinge optimiert werden. Die Art und Weise, wie dies geschieht, ist interessant.
Zunächst werden die Indizes berechnet, aus denen die Elemente entfernt werden sollen. Dies erfolgt, indem zuerst ein winziges BitSet
Array von long
Werten berechnet wird, in dem sich an jedem Index ein 64 bit
Wert (a long
) befindet. Mehrere 64 bit
Werte machen dies a BitSet
. Um einen Wert auf einen bestimmten Versatz festzulegen, müssen Sie zuerst den Index im Array ermitteln und dann das entsprechende Bit setzen. Das ist nicht sehr kompliziert. Angenommen, Sie möchten Bit 65 und 3 setzen. Zuerst benötigen wir ein long [] l = new long[2]
(weil wir über 64 Bit hinausgegangen sind, aber nicht mehr als 128):
|0...(60 more bits here)...000|0...(60 more bits here)...000|
Sie finden zuerst den Index: 65 / 64
(sie tun es tatsächlich 65 >> 6
) und geben dann in diesen Index ( 1
) das benötigte Bit ein:
1L << 65 // this will "jump" the first 64 bits, so this will actually become 00000...10.
Gleiches für 3
. Als solches wird dieses lange Array:
|0...(60 more bits here)...010|0...(60 more bits here)...1000|
Im Quellcode nennen sie dieses BitSet - deathRow
(schöner Name!).
Nehmen wir das even
Beispiel hier, wolist = 2, 4, 6, 5, 5
- Sie iterieren das Array und berechnen dies
deathRow
(woPredicate::test
isttrue
).
DeathRow = 7 (000 ... 111)
Bedeutungsindizes = [0, 1, 2] sind zu entfernen
- Sie ersetzen jetzt Elemente im zugrunde liegenden Array basierend auf dieser DeathRow (ohne auf die Details einzugehen, wie dies gemacht wird).
Das innere Array wird zu: [5, 5, 6, 5, 5]. Grundsätzlich verschieben sie die Elemente, die vor dem Array bleiben sollen.
Ich kann endlich die Frage stellen.
Zu diesem Zeitpunkt wissen sie:
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
Für mich gibt es hier einen einzigen Schritt:
void getRidOfElementsFromWToEnd() {
for(int i=w; i<end; ++i){
es[i] = null;
}
size = w;
}
Stattdessen passiert Folgendes:
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;
}
Ich habe die Variablen hier absichtlich umbenannt.
Was ist der Sinn des Anrufs:
System.arraycopy(es, end, es, w, size - end);
Vor allem size - end
, da end
es die size
ganze Zeit ist - es wird nie geändert (so ist das immer zero
). Dies ist hier im Grunde ein NO-OP. Welchen Eckfall vermisse ich hier?
System.arraycopy(es, end, es, w, size - end)
als zugrunde liegende Implementierungsdetails vonremoveIf
? Ich hatte fast das Gefühl, dazwischen eine Antwort auf eine andere Frage zu lesen. (Den Kommentar oben lesen) Ich spüre, dass es schließlich zu einer trivialen Frage kam. Ist das so?System.arrayCopy
. Trotzdem war es eine lustige Reise durch die Details (dieses interne Bit-Set hat die gleiche Idee wiejava.util.BitSet
)range
...) und ich werde es akzeptieren.java.util.BitSet
. Für michBitSet
sieht die Neuimplementierung der Operationen nicht wesentlich besser aus als das Original. Die Möglichkeit, ganze Wörter zu überspringen, wurde verpasst.Antworten:
Sie betrachten den spezifischen (allgemeinen) Fall, dass die Liste, die Sie aufrufen, mit der Liste
removeIf
identisch istArrayList
. Nur in diesem Fall können Sie davon ausgehen, dass diesend
immer gleich istsize
.Ein Gegenbeispiel wäre:
Ebenso
removeAll
wirdshiftTailOverGap
mit einemend
Argument aufgerufen, das sich vonsize
der Anwendung auf a unterscheiden kannsubList
.Eine ähnliche Situation ergibt sich, wenn Sie anrufen
clear()
. In diesem Fall ist die eigentliche Operation, die beim Aufrufen vonArrayList
selbst ausgeführt wird, so trivial, dass dieshiftTailOverGap
Methode nicht einmal aufgerufen wird. Nur wenn Sie so etwas wie verwendenl.subList(a, b).clear()
, wird es aufremoveRange(a, b)
on landenl
, was wiederum, wie Sie bereits selbst herausgefunden haben,shiftTailOverGap(elementData, a, b)
mit einem aufgerufen wird,b
das kleiner sein kann alssize
.quelle