La façon la plus efficace (performance) de supprimer de nombreux éléments de la Liste en Java?
J'ai assez grande Liste des éléments nommés (>= 1 000 000 d'items) et une condition notée par <dir> qui permet de sélectionner les éléments à supprimer et <dir> est vrai pour beaucoup (peut-être la moitié) des articles sur ma liste.
Mon but est de supprimer efficacement les éléments sélectionnés par <dir>, et de conserver tous les autres éléments, la liste des sources ne peut être modifiée, nouvelle liste peut être créée - la meilleure façon de le faire, il doit être choisie en tenant compte de la performance.
Voici mon code de test:
System.out.println("preparing items");
List<Integer> items = new ArrayList<Integer>(); //Integer is for demo
for (int i = 0; i < 1000000; i++) {
items.add(i * 3); //just for demo
}
System.out.println("deleting items");
long startMillis = System.currentTimeMillis();
items = removeMany(items);
long endMillis = System.currentTimeMillis();
System.out.println("after remove: items.size=" + items.size() +
" and it took " + (endMillis - startMillis) + " milli(s)");
et naïf de mise en œuvre:
public static <T> List<T> removeMany(List<T> items) {
int i = 0;
Iterator<T> iter = items.iterator();
while (iter.hasNext()) {
T item = iter.next();
//<cond> goes here
if (/*<cond>: */i % 2 == 0) {
iter.remove();
}
i++;
}
return items;
}
Comme vous pouvez le voir, j'ai utilisé index de l'élément modulo 2 == 0 comme supprimer la condition (<dir>) - juste pour démonstation fins.
Quoi de mieux que la version de removeMany
peuvent être fournis et pourquoi cette meilleure version est en fait mieux?
- Quelles mesures de performance en la matière - il suffit de vitesse, ou est l'utilisation de la mémoire important? C'est la liste de courte durée? Chaque entrée de la plus courte (post-supprimer) liste garantis pour être accessible? Je me demande si la création d'une nouvelle liste itérateur qui stocke la suppression de la condition comme un retour condition peut être une solution efficace pour une certaine classe de problèmes. Au lieu de supprimer de la liste, vous pourriez avoir l'itérateur de la méthode next() ignorer les éléments qui ne correspondent pas à l'état. Cela aurait l'avantage de test des entrées sur lequel vous opérez, avec la peine de perdre beaucoup de mémoire.
- tout comme dans l'exemple ci-dessus: l'entrée est une liste, la sortie est une liste (même avec des éléments sélectionnés supprimés ou une nouvelle conservé des éléments) et la vitesse est ma plus important métriques.
- merci pour vos réponses! je viens de donner ma réponse, qui compile les différentes approches proposées et les tests dans la pratique. j'espère que mon code ne contient pas d'erreurs et mes conclusions finales sont utiles.
Vous devez vous connecter pour publier un commentaire.
Comme d'autres l'ont dit, votre premier réflexe devrait être de construire une deuxième liste.
Mais, si vous voulez aussi essayer de modifier la liste en place, la façon efficace de le faire est d'utiliser
Iterables.removeIf()
de Goyave. Si son argument est une liste, il fusionne les éléments conservés vers l'avant, puis tout simplement les côtelettes à la fin, beaucoup plus rapide que la suppression() intérieur les éléments un par un.En supprimant un grand nombre d'éléments à partir d'un
ArrayList
est unO(n^2)
opération. Je recommanderais tout simplement à l'aide d'unLinkedList
c'est plus optimisé pour l'insertion et le retrait (mais pas pour l'accès aléatoire). LinkedList a un peu d'une surcharge de la mémoire.Si vous avez besoin de garder
ArrayList
, alors vous êtes mieux de créer une nouvelle liste.Mise à jour: la Comparaison avec la création d'une nouvelle liste:
La réutilisation de la même liste, le coût principal est à venir à partir de la suppression du nœud et la mise à jour appropriée des pointeurs dans LinkedList. C'est un fonctionnement constant pour tout nœud.
Lors de la construction d'une nouvelle liste, le coût principal est à venir à partir de la création de la liste, et l'initialisation de tableau entrées. Les deux sont bon marché des opérations. Vous pourriez incurre le coût de redimensionnement de la nouvelle liste backend tableau; en supposant que le tableau final est supérieur à la moitié de l'entrée de gamme.
Donc, si vous deviez supprimer un seul élément, puis
LinkedList
approche est probablement plus rapide. Si vous supprimez tous les nœuds sauf pour l'un, probablement à la liste nouvelle approche est plus rapide.Il y a plus de complications lorsque vous apportez de la gestion de la mémoire et de la GC. Je voudrais laisser ces.
La meilleure option est de mettre en œuvre les solutions de rechange vous-même et à comparer les résultats lors de l'exécution de votre charge typique.
remove()
sur l'élément d'une liste de tableaux, vous devez remplacer la valeur à l'indice actuel avec l'élément à la queue de la liste. Et puis remove() sur le dernier élément. Cela provoque pas d'éléments à être changé, mais n'modifier l'ordre des éléments dans la liste.O(n)
opérations sont "équivalentes" à l'infini, mais dans la pratique, ils diffèrent beaucoup.Je voudrais faire une nouvelle
List
à ajouter les éléments à la, depuis la suppression d'un élément à partir du milieu de la Liste est assez cher.EDIT: je n'ai pas testé, donc il peut très bien être de petites erreurs de syntaxe.
Deuxième EDIT: à l'Aide d'un
LinkedList
est mieux quand vous n'avez pas besoin d'un accès aléatoire mais rapide ajouter fois.MAIS...
Le facteur constant pour
ArrayList
est plus petite que celle pourLinkedList
(Ref). Puisque vous pouvez faire un raisonnable suppose de la façon dont de nombreux éléments seront supprimés (vous avez dit "environ la moitié" dans votre question), l'ajout d'un élément à la fin d'uneArrayList
est O(1) tant que vous n'avez pas à re-attribuer. Donc, si vous peut rendre raisonnable suppose, j'attendrais laArrayList
à être légèrement plus rapide que laLinkedList
dans la plupart des cas. (Pour le code que j'ai posté. Dans votre naïveté implementatation, je pense queLinkedList
sera plus rapide).J'imagine que la construction d'une nouvelle liste, plutôt que de modifier la liste existante, serait plus performant - en particulier lorsque le nombre d'éléments est aussi grand que vous indiquez. Cela suppose, votre liste est une
ArrayList
, pas unLinkedList
. Pour un non-circulaireLinkedList
, de l'insertion est O(n), mais l'enlèvement de l'existant iterator position est O(1); dans ce cas, votre naïfs algorithme doit être suffisamment performant.À moins que la liste est un
LinkedList
, le coût de déplacement de la liste à chaque fois que vous appelezremove()
est probablement l'un des composants les plus chers de la mise en œuvre. Pour le tableau des listes, je voudrais envisager d'utiliser:i++
.for (T item : items)
. Un itérateur est utile uniquement si vous pourrait être l'appel de rir.remove() de l'omi.Je suis désolé, mais toutes les réponses sont à côté de la question, je pense: Vous avez probablement n'avez pas à, et ne devrait probablement pas le cas, utilisez une Liste.
Si ce genre de "requête" est commun, pourquoi ne pas construire un commandés structure de données qui élimine le besoin de parcourir tous les nœuds de données? Vous ne nous dites pas assez sur le problème, mais compte tenu de l'exemple, vous fournir un simple arbre pourrait faire l'affaire. Il y a une insertion frais généraux par élément, mais vous pourrez très rapidement trouver le sous-arbre contenant les nœuds qui correspondent , et par conséquent, vous pouvez éviter la plupart des comparaisons que vous faites maintenant.
En outre:
Selon la nature exacte du problème, et la structure de données que vous définissez, vous pouvez accélérer la suppression -- si les nœuds que vous voulez tuer les faire réduire à un sous-arbre ou quelque chose du genre, vous avez juste baisse que le sous-arbre, plutôt que de mettre à jour toute une série de la liste des nœuds.
Chaque fois que vous retirez un élément de la liste, mise à jour des pointeurs -- par exemple lastNode.prochaine et nextNode.prev ou quelque chose, mais si il s'avère que vous souhaitez également supprimer le nextNode, puis le pointeur de la mise à jour que vous simplement la conséquence est jeté par une nouvelle mise à jour.)
Une chose que vous pourriez faire est d'essayer d'utiliser un
LinkedList
au lieu d'uneArrayList
, comme avec unArrayList
tous les autres éléments doivent être copiés si des éléments sont supprimés à partir de l'intérieur de la liste.LinkedList
ou la suppression d'unLinkedList
devrait être le même, car les deux sont en O(1), et de vous retirer de la moitié des éléments. Si vous retirez plus d'éléments que vous quittez, l'ajout d'une nouvelle liste devrait être plus rapide, et vice versa.Utilisation Apache Commons Collections. Plus précisément cette fonction. Ceci est mis en œuvre essentiellement de la même manière que les gens sont ce qui suggère que vous la mettre en œuvre (c'est à dire créer une nouvelle liste, puis ajouter).
Car la vitesse est métriques les plus importantes, il y a la possibilité d'utiliser plus de mémoire et de faire moins de loisirs de listes (comme mentionné dans mon commentaire). Réel impact sur les performances doit être entièrement dépendante de la façon dont la fonctionnalité est utilisé, cependant.
L'algorithme suppose qu'au moins une des conditions suivantes est remplie:
Avertissement: Il y a pro les erreurs de syntaxe, je ne l'ai pas essayer de compiler quoi que ce soit.
Tout d'abord, la sous-classe ArrayList
Alors nous avons besoin de l'aide des classes:
et, bien sûr, nous avons besoin de l'état de l'interface:
Et une condition pour vérifier
et nous sommes enfin prêts pour quelque test code
Peut-être une Liste n'est pas optimal structure de données pour vous? Pouvez-vous changer? Peut-être vous pouvez utiliser un arbre où les éléments sont triés dans une manière que la suppression d'un nœud supprime tous les éléments qui répondent à la condition? Ou qu'au moins la vitesse de vos opérations?
Dans votre exemple simpliste à l'aide de deux listes (une avec les articles i % 2 != 0 est vraie et l'autre avec les articles i % 2 != 0 est faux) pourrait bien servir. Mais c'est bien sûr très domaine de la dépendance.
Plutôt que de troubler ma première réponse, qui est déjà assez long, voici une seconde, option: vous pouvez créer votre propre liste de tableaux, et le drapeau des choses comme "supprimé". Cet algorithme fait les hypothèses:
Aussi, c'est, encore une fois, pas testé donc il n'y a prlolly les erreurs de syntaxe.
et l'itérateur - plus de travail peut être nécessaire pour le garder synchronisés, et de nombreuses méthodes sont laissés de côté, cette fois:
Essayer de la mise en œuvre de la récursivité dans votre algorithme.