Leistung von contains () in einem HashSet vs. ArrayList

Leistung von contain () in einem HashSet vs ArrayList

1. Einführung

In dieser Kurzanleitung werdenwe’re going to discuss the performance of the contains() method available in java.util.HashSet and java.util.ArrayList. Sie sind beide Sammlungen zum Speichern und Bearbeiten von Objekten.

HashSet ist eine Sammlung zum Speichern eindeutiger Elemente. Um mehr über dieHashSet, zu erfahren, schauen Sie sichthis link an.

ArrayList ist eine beliebte Implementierung derjava.util.List-Schnittstelle.

Wir haben einen erweiterten Artikel über die verfügbarenArrayListhere.

2. HashSet.contains()

Intern basiert die Implementierung vonHashSetauf einerHashMap -Sinstanz. Die Methodecontains() ruftHashMap.containsKey(object) auf.

Hier wird geprüft, ob sichobject in der internen Zuordnung befindet oder nicht. Die interne Karte speichert Daten in den Knoten, die als Buckets bezeichnet werden. Jeder Bucket entspricht einem Hash-Code, der mithashCode() method generiert wurde. contains() verwendet also tatsächlich die MethodehashCode() , um dieobject’s -Slokation zu ermitteln.

Bestimmen wir nun die Komplexität der Suchzeit. Stellen Sie sicher, dass Sie mitBig-O notation vertraut sind, bevor Sie fortfahren.

Im Durchschnittthe 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.

Dies ist eine Verbesserung gegenüber Java 7, bei demLinkedList für die interne Bucket-Struktur verwendet wurden. Im Allgemeinen sind Hashcode-Kollisionen selten. Wir können also die Komplexität der Elementensuche alsO(1) betrachten.

3. ArrayList.c ontains ()

InternArrayList uses the indexOf(object) method to check if the object is in the list. Die MethodeindexOf(object) iteriert das gesamte Array und vergleicht jedes Element mit der Methodeequals(object).

Zurück zur Komplexitätsanalyse: Die MethodeArrayList.contains()benötigtO(n)Zeit. So the time we spend to find a specific object here depends on the number of items we have in the array.

4. Benchmark-Tests

Lassen Sie uns nun die JVM mit dem Leistungsbenchmark-Test aufwärmen. Wir verwenden das OpenJDK-Produkt JMH (Java Microbenchmark Harness). Weitere Informationen zum Einrichten und Ausführen finden Sie in unserenuseful guide.

Erstellen wir zunächst eine einfacheCollectionsBenchmark-Klasse:

@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);
        }
    }
}

Hier erstellen und initialisieren wirHashSet undArrayList vonEmployee Objekten:

public class Employee {

    private Long id;
    private String name;

    // constructor and getter setters go here
}

Wir fügen beiden Sammlungen dieemployee = new Employee(100L, “Harry”) -Sinstanz als letzte Elemente hinzu. Daher testen wir die Suchzeit des Objektsemployeeauf den schlimmsten Fall.

@OutputTimeUnit(TimeUnit.NANOSECONDS) gibt an, dass wir die Ergebnisse in Nanosekunden wünschen. Die Anzahl der Standarditerationen von@Warmupbeträgt in unserem Fall 5. @BenchmarkMode wird aufMode.AverageTime gesetzt, was bedeutet, dass wir an der Berechnung einer durchschnittlichen Laufzeit interessiert sind. Bei der ersten Ausführung haben wiriterations = 1000 Elemente in unsere Sammlungen aufgenommen.

Danach fügen wir unsere Benchmark-Methoden der KlasseCollectionsBenchmarkhinzu:

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

Hier prüfen wir, ob dasemployeeList dasemployee Objekt enthält.

Ebenso haben wir den bekannten Test füremployeeSet:

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

Schließlich können wir den Test ausführen:

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

Hier sind die Ergebnisse:

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

Wir können deutlich sehen, dass die MethodetestArrayList einen durchschnittlichen Suchwert von4035.646 nsaufweist, währendtestHashSet mit9.456 ns im Durchschnitt schneller abschneidet.

Erhöhen wir nun die Anzahl der Elemente in unserem Test und führen Sie sie für Iterationen = 10.000 Elemente aus:

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

Auch hier hatcontains() inHashSet einen enormen Leistungsvorteil gegenüberArrayList.

5. Fazit

Diese kurze Beschreibung erklärt die Leistung dercontains()-Methode derHashSet- undArrayList-Sammlungen. Mithilfe des JMH-Benchmarking haben wir die Leistung voncontains() für jeden Sammlungstyp dargestellt.

Als Schlussfolgerung können wir lernen, dassthe contains() method works faster in HashSet compared to an ArrayList.

Wie üblich lautet der vollständige Code für diesen Artikelover on GitHub project.