Créer une liste distincte de la liste existante en Java 7 et 8?
Si j'ai:
List<Integer> listInts = { 1, 1, 3, 77, 2, 19, 77, 123, 14, 123... }
en Java, Quelle est la manière efficace de créer un <!-- 2-ne contenant que le distinct valeurs de listInts
?
Ma pensée est de créer un Set<Integer> setInts
contenant toutes les valeurs de listInts
alors appelez List<Integer> listDistinctInts = new ArrayList<>(setInts);
mais cela semble potentiellement inefficace - Existe-t-il une meilleure solution en utilisant Java 7?
Je n'utilise pas Java 8, mais je crois que je pourrais faire quelque chose comme ce(?):
List<Integer> listDistinctInts = listInts.stream().distinct().collect(Collectors.toList());
cela Serait-il plus performant que l'approche ci-dessus et/ou est-il un moyen plus efficace de le faire en Java 8?
enfin, (et je suis conscient que poser plusieurs questions pourrait être mal vu mais c'est directement lié) si seulement je me souciais de la count des éléments distincts dans listInts
existe - t-il un moyen plus efficace d'obtenir cette valeur (en Java 7 et 8) - sans d'abord créer une liste ou un ensemble de toutes les éléments?
je suis le plus intéressé par les méthodes Java natives pour accomplir ceci et éviter de réinventer n'importe quelles roues mais je considérerais le code ou les bibliothèques roulées à la main si elles offrent une meilleure clarté ou performance. J'ai lu cette question connexe Java - Liste Distincte des Objets mais il n'est pas tout à fait clair sur les différences de performance entre les approches Java 7 et 8 ou s'il pourrait y avoir de meilleures techniques?
7 réponses
j'ai maintenant micro-comparé la plupart des options proposées à partir des excellentes réponses fournies. Comme la plupart des questions non triviales liées à la performance, la réponse à laquelle il convient de répondre est "ça dépend".
Tous mes tests ont été effectués avec JMH Java Microbenchmarking Harnais.
la plupart de ces tests ont été effectués en utilisant JDK 1.8, bien que j'ai effectué certains des tests avec JDK 1.7 trop juste pour s'assurer que son la performance n'était pas trop différente (elle était presque identique). J'ai testé les techniques suivantes tirées des réponses fournies jusqu'à présent:
1. Java 8 Stream - la solution en utilisant stream()
j'ai eu proprosed comme une possibilité si l'aide de Java8:
public List<Integer> testJava8Stream(List<Integer> listInts) {
return listInts.stream().distinct().collect(Collectors.toList());
}
prosmoderne Java 8 approche, pas de 3ème partie des dépendances
cons Nécessite Java 8
2. Ajouter À La Liste - La solution proposée par Victor2748 où une nouvelle liste est construite et ajoutée, si et seulement si la liste ne contient pas déjà la valeur. Notez que j'ai aussi préallouer la liste de destination à la taille de l'original (le max possible) pour éviter toute réaffectation:
public List<Integer> testAddingToList(List<Integer> listInts) {
List<Integer> listDistinctInts = new ArrayList<>(listInts.size());
for(Integer i : listInts)
{
if( !listDistinctInts.contains(i) ) { listDistinctInts.add(i); }
}
return listDistinctInts;
}
pros fonctionne dans N'importe quelle version de Java, pas besoin de créer un ensemble et puis la copie, pas de 3ème partie deps
consdoit vérifier à plusieurs reprises la liste des valeurs existantes pendant que nous la construisons
3. GS Collections Fast(Eclipse collections) - La solution proposée par Craig P. Motlincollection GS bibliothèque et leur type de liste personnalisé FastList
:
public List<Integer> testGsCollectionsFast(FastList listFast)
{
return listFast.distinct();
}
pros apparemment très rapide, code expressif simple, fonctionne en Java 7 et 8
cons nécessite une bibliothèque tierce partie et un FastList
plutôt que List<Integer>
4. Collection GS adaptée - la solution FastList ne comparait pas tout à fait comme-pour-comme parce qu'elle avait besoin d'un FastList
passé à la méthode plutôt que d'un bon ol' ArrayList<Integer>
donc J'ai aussi testé la méthode de l'adaptateur Craig proposé:
public List<Integer> testGsCollectionsAdapted(List<Integer> listInts)
{
return listAdapter.adapt(listInts).distinct();
}
prosNe nécessite pas un FastList
, travaille dans Java 7 et 8
cons doit adapter la liste donc peut ne pas fonctionner aussi bien, a besoin de bibliothèque tierce partie
5. Goyave ImmutableSet - La méthode proposée par Louis Wasserman dans les commentaires, et par փ Shengyuan lu dans leur réponse en utilisant Goyave:
public List<Integer> testGuavaImmutable(List<Integer> listInts)
{
return ImmutableSet.copyOf(listInts).asList();
}
pros apparemment très rapide, fonctionne en Java 7 ou 8
consrenvoie un Immutable List
, ne peut pas gérer la valeur null dans la Liste d'entrée, et exige de la 3e partie de la bibliothèque
7. HashSet - mon idée originale (aussi recommandé par EverV0id, ulix et Radiodef)
public List<Integer> testHashSet(List<Integer> listInts)
{
return new ArrayList<Integer>(new HashSet<Integer>(listInts));
}
prosFonctionne en Java 7 et 8, pas de 3ème partie des dépendances
cons ne conserve pas l'ordre original de la liste, doit construire set puis Copier vers list.
6. LinkedHashSet Depuis le HashSet
la solution n'est pas de préserver l'ordre des nombres Entiers dans la liste d'origine j'ai aussi testé une version qui utilise LinkedHashSet à préserver ordre:
public List<Integer> testLinkedHashSet(List<Integer> listInts)
{
return new ArrayList<Integer>(new LinkedHashSet<Integer>(listInts));
}
pros conserve la commande originale, fonctionne en Java 7 et 8, aucune dépendance de tierce partie
consRare d'être aussi rapide que régulier HashSet
approche
Résultats
Voici mes résultats pour différentes tailles de listInts
(résultats ordonnés du plus lent au plus rapide):
1. prendre distincte de la liste de tableaux de 100 000 aléatoires entiers entre 0 à 50,000 (ie. grande liste, quelques doublons)
Benchmark Mode Samples Mean Mean error Units
AddingToList thrpt 10 0.505 0.012 ops/s
Java8Stream thrpt 10 234.932 31.959 ops/s
LinkedHashSet thrpt 10 262.185 16.679 ops/s
HashSet thrpt 10 264.295 24.154 ops/s
GsCollectionsAdapted thrpt 10 357.998 18.468 ops/s
GsCollectionsFast thrpt 10 363.443 40.089 ops/s
GuavaImmutable thrpt 10 469.423 26.056 ops/s
2. en prenant distinct de ArrayList de 1000 ints aléatoires entre 0-50 (c.-à-d. moyen de la liste, de nombreux doublons)
Benchmark Mode Samples Mean Mean error Units
AddingToList thrpt 10 32794.698 1154.113 ops/s
HashSet thrpt 10 61622.073 2752.557 ops/s
LinkedHashSet thrpt 10 67155.865 1690.119 ops/s
Java8Stream thrpt 10 87440.902 13517.925 ops/s
GsCollectionsFast thrpt 10 103490.738 35302.201 ops/s
GsCollectionsAdapted thrpt 10 143135.973 4733.601 ops/s
GuavaImmutable thrpt 10 186301.330 13421.850 ops/s
3. prendre distinct de ArrayList de 100 ints aléatoires entre 0-100 (c.-à-d. petite liste, quelques doublons)
Benchmark Mode Samples Mean Mean error Units
AddingToList thrpt 10 278435.085 14229.285 ops/s
Java8Stream thrpt 10 397664.052 24282.858 ops/s
LinkedHashSet thrpt 10 462701.618 20098.435 ops/s
GsCollectionsAdapted thrpt 10 477097.125 15212.580 ops/s
GsCollectionsFast thrpt 10 511248.923 48155.211 ops/s
HashSet thrpt 10 512003.713 25886.696 ops/s
GuavaImmutable thrpt 10 1082006.560 18716.012 ops/s
4. prise distincte de L'ArrayList de 10 ints aléatoires entre 0-50 (c.-à-d. minuscule liste, quelques doublons)
Benchmark Mode Samples Mean Mean error Units
Java8Stream thrpt 10 2739774.758 306124.297 ops/s
LinkedHashSet thrpt 10 3607479.332 150331.918 ops/s
HashSet thrpt 10 4238393.657 185624.358 ops/s
GsCollectionsAdapted thrpt 10 5919254.755 495444.800 ops/s
GsCollectionsFast thrpt 10 7916079.963 1708778.450 ops/s
AddingToList thrpt 10 7931479.667 966331.036 ops/s
GuavaImmutable thrpt 10 9021621.880 845936.861 ops/s
Conclusions
si vous prenez seulement les éléments distincts d'une liste une fois, et la liste n'est pas très longue de ces méthodes devrait être suffisant.
les approches générales les plus efficaces proviennent des bibliothèques de tiers: collections GS et goyaves effectuées admirablement.
vous devrez peut-être tenir compte de la taille de votre liste et du nombre probable de doublons lors de la sélection de la méthode la plus performante.
l'approche naïve d'ajouter à une nouvelle liste seulement si la valeur n'y est pas déjà fonctionne très bien pour les listes minuscules, mais dès que vous avez plus d'une poignée de valeurs dans la liste d'entrée elle exécute le pire des méthodes essayées.
La Goyave
ImmutableSet.copyOf(listInts).asList()
la méthode fonctionne le plus rapidement dans la plupart des situations. Mais prendre note des restrictions: la liste retournée estImmutable
et la liste des entrées ne peut pas contenir nulls.HashSet
method exécute le meilleur des approches non tierces et généralement mieux que les flux Java 8, mais réordonne les entiers (qui peuvent ou non être un problème selon votre cas d'utilisation).LinkedHashSet
l'approche maintient l'ordre mais sans surprise était généralement pire que la méthode du HashSet.les Deux
HashSet
etLinkedHashSet
les méthodes fonctionneront plus mal lorsque vous utilisez des listes de types de données qui ont des calculs complexes de HashCode, alors faites votre propre profilage si vous essayez de choisir distinctFoo
s deList<Foo>
.Si vous avez déjà collections GS en tant que dépendance, il fonctionne très bien et est plus flexible que L'ImmutableList Goyave approche. Si vous ne l'avez pas comme une dépendance, il vaut la peine d'envisager de l'ajouter si la performance de sélectionner des éléments distincts est critique pour la performance de votre application.
de manière décevante, Java 8 streams semble donner des résultats assez médiocres. Il y a peut-être une meilleure façon de coder le
distinct()
appeler que la façon dont j'ai utilisé, de sorte que les commentaires ou d'autres réponses sont bien sûr les bienvenus.
NB. Je ne suis pas expert chez MicroBenchmarking, donc si quelqu'un trouve des défauts dans mes résultats ou la méthodologie s'il Vous Plaît me prévenir et je vais essayer de corriger la réponse.
Si vous utilisez Collections Eclipse (anciennement collections GS), vous pouvez utiliser la méthode distinct()
.
ListIterable<Integer> listInts = FastList.newListWith(1, 1, 3, 77, 2, 19, 77, 123, 14, 123);
Assert.assertEquals(
FastList.newListWith(1, 3, 77, 2, 19, 123, 14),
listInts.distinct());
l'avantage d'utiliser distinct()
au lieu de convertir en un ensemble puis de revenir à une liste est que distinct()
préserve l'ordre de la liste originale, en conservant la première occurrence de chaque élément. Il est mis en œuvre en utilisant à la fois un ensemble et une liste.
MutableSet<T> seenSoFar = UnifiedSet.newSet();
int size = list.size();
for (int i = 0; i < size; i++)
{
T item = list.get(i);
if (seenSoFar.add(item))
{
targetCollection.add(item);
}
}
return targetCollection;
si vous ne pouvez pas convertir votre liste originale en GS Type de Collections, vous pouvez utiliser ListAdapter pour obtenir la même API.
MutableList<Integer> distinct = ListAdapter.adapt(integers).distinct();
Il n'y a aucun moyen d'éviter la création de l'Ensemble. Néanmoins, UnifiedSet est plus efficace que HashSet, il y aura donc un avantage de vitesse.
Si tout ce que vous voulez, c'est le d'éléments distincts, il est plus efficace de simplement créer un ensemble sans créer la liste.
Verify.assertSize(7, UnifiedSet.newSet(listInts));
Eclipse Collections 8.0 nécessite Java 8. Collections Eclipse 7.X fonctionne bien avec Java 8, mais ne nécessite que Java 5.
Remarque: je suis un committer pour Eclipse collections.
Goyave peut être votre choix:
ImmutableSet<Integer> set = ImmutableSet.copyOf(listInts);
L'API est extrêmement optimisée.
C'est plus rapide que listInts.stream().distinct()
et new LinkedHashSet<>(listInts)
.
en ajoutant une valeur à un listInts
cocher:
int valueToAdd;
//...
if (!listInts.contains(valueToAdd)) {listInts.add(valueToAdd)}
si vous avez une liste existante, utiliser un pour chaque déclaration pour copier toutes les valeurs de cette liste, à nouveau, que vous voulez être "distinctes":
List<Integer> listWithRepeatedValues;
List<Integer> distinctList;
//...
for (Integer i : listWithRepeatedValues) {
if (!listInts.contains(valueToAdd)) {distinctList.add(i);}
}
Ne vous inquiétez pas. L'utilisation d'un HashSet est un moyen simple et efficace pour éliminer les doublons:
Set<Integer> uniqueList = new HashSet<>();
uniqueList.addAll(listInts); // Add all elements eliminating duplicates
for (int n : uniqueList) // Check the results (in no particular order)
System.out.println(n);
System.out.println("Number distinct values: " + uniqueList.size());
dans un scénario plus spécifique, juste au cas où l'intervalle des valeurs possibles est connu, n'est pas très grand, alors que listInts
est très grand.
La façon la plus efficace de compter le nombre d'entrées uniques dans la liste que je peux penser est:
boolean[] counterTable = new boolean[124];
int counter = 0;
for (int n : listInts)
if (!counterTable[n]) {
counter++;
counterTable[n] = true;
}
System.out.println("Number of distinct values: " + counter);
Cela devrait fonctionner:
yourlist.flux.)(map (votre wrapper qui remplace la méthode equals et hashchode::new).distinct.)(carte(wrapper définis ci-dessus::méthode qui retourne le résultat final).recueillir des(Collectionneurs.toList ());