Performance de contains () dans un HashSet vs ArrayList

Performances de contains () dans un HashSet vs ArrayList

1. introduction

Dans ce guide rapide,we’re going to discuss the performance of the contains() method available in java.util.HashSet and java.util.ArrayList. Ce sont deux collections pour stocker et manipuler des objets.

HashSet est une collection pour stocker des éléments uniques. Pour en savoir plus sur lesHashSet,, consultezthis link.

ArrayList est une implémentation populaire de l'interfacejava.util.List.

Nous avons un article détaillé sur lesArrayList disponibleshere.

2. HashSet.contains()

En interne, l'implémentation deHashSet est basée sur une instanceHashMap . La méthodecontains() appelleHashMap.containsKey(object).

Ici, il vérifie si leobject est dans la carte interne ou non. La carte interne stocke des données à l'intérieur des nœuds, appelés seaux. Chaque bucket correspond à un code de hachage généré avechashCode() method. Donccontains() utilise en fait la méthodehashCode() pour trouver l'emplacementobject’s .

Déterminons maintenant la complexité du temps de recherche. Avant de continuer, assurez-vous que vous êtes familier avecBig-O notation.

En moyenne,the contains() of HashSet runs in O(1) time. Getting the object’s bucket location is a constant time operation. Taking into account possible collisions, the lookup time may rise to log(n) because the internal bucket structure is a TreeMap.

Il s'agit d'une amélioration par rapport à Java 7 qui utilisait unLinkedList pour la structure interne du bucket. En général, les collisions de code de hachage sont rares. Nous pouvons donc considérer la complexité de la recherche d'éléments commeO(1).

3. ArrayList.c ontains ()

En interne,ArrayList uses the indexOf(object) method to check if the object is in the list. La méthodeindexOf(object) itère le tableau entier et compare chaque élément avec la méthodeequals(object).

Pour revenir à l'analyse de complexité, la méthodeArrayList.contains() nécessiteO(n) temps. So the time we spend to find a specific object here depends on the number of items we have in the array.

4. Test de référence

Maintenant, réchauffons la JVM avec le test de performance. Nous utiliserons le produit OpenJDK JMH (Java Microbenchmark Harness). Pour en savoir plus sur la configuration et l'exécution, consultez nosuseful guide.

Pour commencer, créons une simple classeCollectionsBenchmark:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5)
public class CollectionsBenchmark {

    @State(Scope.Thread)
    public static class MyState {
        private Set employeeSet = new HashSet<>();
        private List employeeList = new ArrayList<>();

        private long iterations = 1000;

        private Employee employee = new Employee(100L, "Harry");

        @Setup(Level.Trial)
        public void setUp() {

            for (long i = 0; i < iterations; i++) {
                employeeSet.add(new Employee(i, "John"));
                employeeList.add(new Employee(i, "John"));
            }

            employeeList.add(employee);
            employeeSet.add(employee);
        }
    }
}

Ici, nous créons et initialisonsHashSet et unArrayList d'objetsEmployee:

public class Employee {

    private Long id;
    private String name;

    // constructor and getter setters go here
}

Nous ajoutons la sinstanceemployee = new Employee(100L, “Harry”) comme dernier élément des deux collections. Nous testons donc le temps de recherche de l’objetemployee dans le pire des cas.

@OutputTimeUnit(TimeUnit.NANOSECONDS) indique que nous voulons les résultats en nanosecondes. Le nombre d'itérations par défaut@Warmup est de 5 dans notre cas. Le@BenchmarkMode est défini surMode.AverageTime, ce qui signifie que nous sommes intéressés par le calcul d'un temps de fonctionnement moyen. Pour la première exécution, nous mettons les élémentsiterations = 1000 dans nos collections.

Ensuite, nous ajoutons nos méthodes de référence à la classeCollectionsBenchmark:

@Benchmark
public boolean testArrayList(MyState state) {
    return state.employeeList.contains(state.employee);
}

Ici, nous vérifions si leemployeeList contient l'objetemployee.

De même, nous avons le test familier pouremployeeSet:

@Benchmark
public boolean testHashSet(MyState state) {
    return state.employeeSet.contains(state.employee);
}

Enfin, nous pouvons lancer le test:

public static void main(String[] args) throws Exception {
    Options options = new OptionsBuilder()
      .include(CollectionsBenchmark.class.getSimpleName())
      .forks(1).build();
    new Runner(options).run();
}

Voici les résultats:

Benchmark                           Mode  Cnt     Score     Error  Units
CollectionsBenchmark.testArrayList  avgt   20  4035.646 ± 598.541  ns/op
CollectionsBenchmark.testHashSet    avgt   20     9.456 ±   0.729  ns/op

Nous pouvons clairement voir que la méthodetestArrayList a un score de recherche moyen de4035.646 ns, tandis que la méthodetestHashSet fonctionne plus rapidement avec9.456 ns en moyenne.

Maintenant, augmentons le nombre d'éléments dans notre test et exécutons-le pour des itérations = 10 000 éléments:

Benchmark                           Mode  Cnt      Score       Error  Units
CollectionsBenchmark.testArrayList  avgt   20  57499.620 ± 11388.645  ns/op
CollectionsBenchmark.testHashSet    avgt   20     11.802 ±     1.164  ns/op

Ici aussi, lecontains() dansHashSet a un énorme avantage de performances par rapport auxArrayList.

5. Conclusion

Cette description rapide explique les performances de la méthodecontains() des collectionsHashSet etArrayList. À l’aide du benchmarking JMH, nous avons présenté les performances decontains() pour chaque type de collection.

En conclusion, nous pouvons apprendre quethe contains() method works faster in HashSet compared to an ArrayList.

Comme d'habitude, le code complet de cet article estover on GitHub project.