J'ai une petite question de détail d'implémentation que je n'arrive pas à comprendre ArrayList::removeIf
. Je ne pense pas que je puisse simplement le dire tel qu'il est sans certaines conditions préalables.
En tant que tel: l'implémentation est fondamentalement un volume remove
, contrairement à ArrayList::remove
. Un exemple devrait rendre les choses beaucoup plus faciles à comprendre. Disons que j'ai cette liste:
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);
Et je voudrais supprimer tous les éléments qui sont pairs. Je pourrais faire:
Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
int elem = iter.next();
if (elem % 2 == 0) {
iter.remove();
}
}
Ou :
list.removeIf(x -> x % 2 == 0);
Le résultat sera le même, mais la mise en œuvre est très différente. Étant donné que le iterator
est une vue de la ArrayList
, chaque fois que j'appelle remove
, le sous-jacent ArrayList
doit être amené à un "bon" état, ce qui signifie que le tableau interne va réellement changer. Encore une fois, à chaque appel de remove
, il y aura des appels en System::arrayCopy
interne.
Sur le contraste, removeIf
c'est plus intelligent. Puisqu'il fait l'itération en interne, il peut rendre les choses plus optimisées. La façon dont cela se fait est intéressante.
Il calcule d'abord les index d'où les éléments sont censés être supprimés. Cela se fait en calculant d'abord un minuscule BitSet
, un tableau de long
valeurs où à chaque index, réside une 64 bit
valeur (a long
). Plusieurs 64 bit
valeurs en font un BitSet
. Pour définir une valeur à un décalage particulier, vous devez d'abord trouver l'index dans le tableau, puis définir le bit correspondant. Ce n'est pas très compliqué. Disons que vous voulez définir les bits 65 et 3. Tout d'abord, nous avons besoin d'un long [] l = new long[2]
(car nous sommes allés au-delà de 64 bits, mais pas plus de 128):
|0...(60 more bits here)...000|0...(60 more bits here)...000|
Vous trouvez d'abord l'index: 65 / 64
(ils le font en fait 65 >> 6
) puis dans cet index ( 1
) mettez le bit nécessaire:
1L << 65 // this will "jump" the first 64 bits, so this will actually become 00000...10.
Même chose pour 3
. En tant que tel, ce long tableau deviendra:
|0...(60 more bits here)...010|0...(60 more bits here)...1000|
Dans le code source, ils appellent ce BitSet - deathRow
(joli nom!).
Prenons cet even
exemple ici, oùlist = 2, 4, 6, 5, 5
- ils itèrent le tableau et le calculent
deathRow
(oùPredicate::test
esttrue
).
deathRow = 7 (000 ... 111)
les indices de signification = [0, 1, 2] doivent être supprimés
- ils remplacent désormais les éléments du tableau sous-jacent en fonction de ce deathRow (sans entrer dans les détails sur la façon de procéder)
tableau intérieur devient: [5, 5, 6, 5, 5]. Fondamentalement, ils déplacent les éléments qui sont censés rester devant le tableau.
Je peux enfin poser la question.
À ce stade, ils savent:
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
Pour moi, il y a une seule étape à faire ici:
void getRidOfElementsFromWToEnd() {
for(int i=w; i<end; ++i){
es[i] = null;
}
size = w;
}
Au lieu de cela, cela se produit:
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;
}
J'ai renommé les variables à dessein ici.
Quel est l'intérêt d'appeler:
System.arraycopy(es, end, es, w, size - end);
Surtout size - end
, puisque end
c'est size
tout le temps - il n'est jamais changé (donc c'est toujours zero
). Il s'agit essentiellement d'un NO-OP ici. Quel étui d'angle me manque ici?
System.arraycopy(es, end, es, w, size - end)
comme détails d'implémentation sous-jacentsremoveIf
? J'avais presque l'impression de lire une réponse à une autre question entre les deux. (En lisant le commentaire ci-dessus) Je sens que cela a finalement abouti à une question banale. Est-ce vrai?System.arrayCopy
. Néanmoins, ce fut un voyage amusant à travers les détails (cet ensemble de bits interne s'avère avoir la même idée quejava.util.BitSet
)range
...) et je l'accepterai.java.util.BitSet
. Pour moi, la réimplémentation desBitSet
opérations ne semble pas beaucoup mieux que l'original. L'occasion de sauter des mots entiers a été manquée.Réponses:
Vous examinez le cas spécifique (commun) où la liste, que vous invoquez
removeIf
, est la même que laArrayList
. Seulement dans ce cas, vous pouvez supposer queend
c'est toujours égal àsize
.Un contre-exemple serait:
De même,
removeAll
appellerashiftTailOverGap
avec unend
argument qui peut différer de celuisize
appliqué à asubList
.Une situation similaire se produit lorsque vous appelez
clear()
. Dans ce cas, l'opération réelle, effectuée lors de son appel sur elle-ArrayList
même, est si triviale qu'elle n'appelle même pas lashiftTailOverGap
méthode. Seulement lorsque vous utilisez quelque chose commel.subList(a, b).clear()
, il va finir àremoveRange(a, b)
surl
, qui à son tour, comme vous avez déjà trouvé vous - même, InvokeshiftTailOverGap(elementData, a, b)
avecb
qui peut être inférieuresize
.la source