Leistung von enthält () in einem HashSet vs ArrayList

1. Einleitung

In dieser Kurzanleitung werden wir die Leistung der in java.util verfügbaren Methode includes () erläutern. HashSet und java.util. ArrayList . Sie sind beide Sammlungen zum Speichern und Bearbeiten von Objekten.

HashSet ist eine Sammlung zum Speichern eindeutiger Elemente. Weitere Informationen zum HashSet finden Sie unter diesem Link.

ArrayList ist eine beliebte Implementierung der Schnittstelle java.util.List .

Wir haben einen erweiterten Artikel über die ArrayList hier verfügbar.

2. HashSet.contains ()

Intern basiert die HashSet- Implementierung auf einer HashMap- Instanz. Das enthält () Methode ruft HashMap.containsKey (Objekt) .

Hier wird geprüft, ob sich das Objekt in der internen Karte befindet oder nicht. Die interne Karte speichert Daten innerhalb der Knoten, die als Buckets bezeichnet werden. Jeder Bucket entspricht einem Hash-Code, der mit der hashCode () -Methode generiert wurde . So enthält () ist eigentlich mit hashCode () Methode des finden Objekt Lage.

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

Im Durchschnitt der enthält () von HashSet in läuft O (1) Zeit . Das Abrufen der Bucket-Position des Objekts ist eine konstante Zeitoperation. Unter Berücksichtigung möglicher Kollisionen kann die Suchzeit auf log (n) ansteigen, da die interne Bucket-Struktur eine TreeMap ist .

Dies ist eine Verbesserung gegenüber Java 7, bei dem eine LinkedList für die interne Bucket-Struktur verwendet wurde. Im Allgemeinen sind Hash-Code-Kollisionen selten. Wir können also die Komplexität der Element-Lookup als O (1) betrachten .

3. ArrayList.c ontains ()

Intern verwendet ArrayList die Methode indexOf (Objekt) , um zu überprüfen, ob sich das Objekt in der Liste befindet . Die Methode indexOf (Objekt) iteriert das gesamte Array und vergleicht jedes Element mit der Methode equals (Objekt) .

Zurück zur Komplexitätsanalyse, der ArrayList . Die Methode enthält () benötigt O (n) Zeit. Die Zeit, die wir hier verbringen, um ein bestimmtes Objekt zu finden, hängt also von der Anzahl der Elemente im Array ab.

4. Benchmark-Tests

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

Erstellen wir zunächst eine einfache CollectionsBenchmark- 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 wir HashSet und eine ArrayList von Employee- Objekten:

public class Employee { private Long id; private String name; // constructor and getter setters go here }

Wir fügen die Instanz employee = new Employee (100L, "Harry") als letzte Elemente zu beiden Sammlungen hinzu. Daher testen wir die Suchzeit des Mitarbeiterobjekts auf den schlimmsten Fall.

@OutputTimeUnit (TimeUnit.NANOSECONDS) gibt an, dass die Ergebnisse in Nanosekunden angezeigt werden sollen. Die Anzahl der standardmäßigen @ Warmup- Iterationen beträgt in unserem Fall 5. Der @BenchmarkMode ist auf Mode.AverageTime eingestellt , was bedeutet, dass wir an der Berechnung einer durchschnittlichen Laufzeit interessiert sind. Für die erste Ausführung fügen wir Iterationen = 1000 Elemente in unsere Sammlungen ein.

Anschließend fügen wir der CollectionsBenchmark- Klasse unsere Benchmark-Methoden hinzu :

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

Hier prüfen wir , ob der employeeList enthält Mitarbeiter - Objekt.

Ebenso haben wir den bekannten Test für employeeSet :

@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 testArrayList Methode hat 4035.646 ns durchschnittlicher Lookup - Score, während die testHashSet führen schneller mit 9,456 ns im Durchschnitt.

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

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 hat das enthält () in HashSet einen großen Leistungsvorteil gegenüber der ArrayList .

5. Schlussfolgerung

In diesem kurzen Artikel wird die Leistung der Methode includes () der Sammlungen HashSet und ArrayList erläutert . Mit Hilfe des JMH-Benchmarking haben wir die Leistung von includes () für jeden Sammlungstyp dargestellt.

Zusammenfassend können wir feststellen, dass die Methode includes () in HashSet im Vergleich zu einer ArrayList schneller funktioniert .

Wie üblich ist der vollständige Code für diesen Artikel im GitHub-Projekt beendet.