Überprüfen Sie, ob ein Java-Array einen Wert enthält

1. Übersicht

In diesem Artikel werden verschiedene Möglichkeiten zum Durchsuchen eines Arrays nach einem bestimmten Wert untersucht.

Wir werden auch vergleichen, wie diese mit JMH (dem Java Microbenchmark Harness) funktionieren, um festzustellen, welche Methode am besten funktioniert.

2. Setup

In unseren Beispielen verwenden wir ein Array, das zufällig generierte Zeichenfolgen für jeden Test enthält:

String[] seedArray(int length) { String[] strings = new String[length]; Random value = new Random(); for (int i = 0; i < length; i++) { strings[i] = String.valueOf(value.nextInt()); } return strings; }

Um das Array in jedem Benchmark wiederzuverwenden, deklarieren wir eine innere Klasse, die das Array und die Anzahl enthält, damit wir den Gültigkeitsbereich für JMH deklarieren können:

@State(Scope.Benchmark) public static class SearchData { static int count = 1000; static String[] strings = seedArray(1000); } 

3. Grundlegende Suche

Drei häufig verwendete Methoden zum Durchsuchen eines Arrays sind eine Liste, ein Satz oder eine Schleife , die jedes Mitglied untersucht, bis es eine Übereinstimmung findet.

Beginnen wir mit drei Methoden, die jeden Algorithmus implementieren:

boolean searchList(String[] strings, String searchString) { return Arrays.asList(SearchData.strings) .contains(searchString); } boolean searchSet(String[] strings, String searchString) { Set stringSet = new HashSet(Arrays.asList(SearchData.strings)); return stringSet.contains(searchString); } boolean searchLoop(String[] strings, String searchString) { for (String string : SearchData.strings) { if (string.equals(searchString)) return true; } return false; }

Wir werden diese Klassenanmerkungen verwenden, um JMH anzuweisen, die durchschnittliche Zeit in Mikrosekunden auszugeben und fünf Aufwärmiterationen auszuführen, um sicherzustellen, dass unsere Tests zuverlässig sind:

@BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 5) @OutputTimeUnit(TimeUnit.MICROSECONDS) 

Und führen Sie jeden Test in einer Schleife aus:

@Benchmark public void searchArrayLoop() { for (int i = 0; i < SearchData.count; i++) { searchLoop(SearchData.strings, "T"); } } @Benchmark public void searchArrayAllocNewList() { for (int i = 0; i < SearchData.count; i++) { searchList(SearchData.strings, "T"); } } @Benchmark public void searchArrayAllocNewSet() { for (int i = 0; i < SearchData.count; i++) { searchSet(SearchData.strings, "S"); } } 

Wenn wir 1000 Suchvorgänge für jede Methode ausführen, sehen unsere Ergebnisse ungefähr so ​​aus:

SearchArrayTest.searchArrayAllocNewList avgt 20 937.851 ± 14.226 us/op SearchArrayTest.searchArrayAllocNewSet avgt 20 14309.122 ± 193.844 us/op SearchArrayTest.searchArrayLoop avgt 20 758.060 ± 9.433 us/op 

Die Schleifensuche ist effizienter als andere. Dies liegt jedoch zumindest teilweise daran, wie wir Sammlungen verwenden.

Wir schaffen eine neue Liste Instanz mit jedem Aufruf von search () und eine neue Liste und eine neue HashSet mit jedem Aufruf SearchSet () . Das Erstellen dieser Objekte verursacht zusätzliche Kosten, die beim Durchlaufen des Arrays nicht anfallen.

4. Effizientere Suche

Was passiert, wenn wir einzelne Instanzen von List and Set erstellen und diese dann für jede Suche wiederverwenden?

Lass es uns versuchen:

public void searchArrayReuseList() { List asList = Arrays.asList(SearchData.strings); for (int i = 0; i < SearchData.count; i++) { asList.contains("T"); } } public void searchArrayReuseSet() { Set asSet = new HashSet(Arrays.asList(SearchData.strings)); for (int i = 0; i < SearchData.count; i++) { asSet.contains("T"); } } 

Wir führen diese Methoden mit denselben JMH-Annotationen wie oben aus und geben die Ergebnisse für die einfache Schleife zum Vergleich an.

Wir sehen sehr unterschiedliche Ergebnisse:

SearchArrayTest.searchArrayLoop avgt 20 758.060 ± 9.433 us/op SearchArrayTest.searchArrayReuseList avgt 20 837.265 ± 11.283 us/op SearchArrayTest.searchArrayReuseSet avgt 20 14.030 ± 0.197 us/op 

Während das Durchsuchen der Liste geringfügig schneller als zuvor ist, sinkt Set auf weniger als 1 Prozent der für die Schleife erforderlichen Zeit!

Nachdem wir die für das Erstellen neuer Sammlungen erforderliche Zeit für jede Suche entfernt haben, sind diese Ergebnisse sinnvoll.

Das Durchsuchen einer Hash-Tabelle, der Struktur, die einem HashSet zugrunde liegt , hat eine zeitliche Komplexität von 0 (1), während ein Array, das der ArrayList zugrunde liegt, 0 (n) ist.

5. Binäre Suche

Eine andere Methode zum Durchsuchen eines Arrays ist eine binäre Suche. Eine binäre Suche ist zwar sehr effizient, erfordert jedoch, dass das Array im Voraus sortiert wird.

Lassen Sie uns das Array sortieren und die binäre Suche versuchen:

@Benchmark public void searchArrayBinarySearch() { Arrays.sort(SearchData.strings); for (int i = 0; i < SearchData.count; i++) { Arrays.binarySearch(SearchData.strings, "T"); } } 
SearchArrayTest.searchArrayBinarySearch avgt 20 26.527 ± 0.376 us/op 

Die binäre Suche ist sehr schnell, jedoch weniger effizient als das HashSet: Die schlechteste Leistung für eine binäre Suche ist 0 (log n), wodurch die Leistung zwischen der einer Array-Suche und einer Hash-Tabelle liegt.

6. Fazit

Wir haben verschiedene Methoden zum Durchsuchen eines Arrays gesehen.

Basierend auf unseren Ergebnissen eignet sich ein HashSet am besten zum Durchsuchen einer Werteliste . Wir müssen sie jedoch im Voraus erstellen und im Set speichern .

Wie immer ist der vollständige Quellcode der Beispiele auf GitHub verfügbar.