Überprüfen Sie, ob ein Java-Array einen Wert enthält
1. Überblick
In diesem Artikel werden verschiedene Möglichkeiten zum Durchsuchen eines Arrays nach einem bestimmten Wert untersucht.
Wir werden auch vergleichen, wie diese mitJMH (dem Java Microbenchmark Harness) funktionieren, um festzustellen, welche Methode am besten funktioniert.
2. Konfiguration
In unseren Beispielen verwenden wir ein Array, das zufällig generierteStrings 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. Einfache Suche
Three commonly used methods for searching an array are as a List, a Set, or with a loop, das 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)
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
The loop search is more efficient than others. Dies liegt jedoch zumindest teilweise daran, wie wir Sammlungen verwenden.
Wir erstellen mit jedem Aufruf vonsearchList() eine neueList-Instanz und mit jedem Aufruf vonsearchSet() eine neueList und eine neueHashSet. Das Erstellen dieser Objekte verursacht zusätzliche Kosten, die beim Durchlaufen des Arrays nicht anfallen.
4. Effizientere Suche
Was passiert, wenn wir einzelne Instanzen vonList undSet erstellen und sie 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-Anmerkungen 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 die Suche nachList geringfügig schneller ist als zuvor, sinktSet 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.
Beim Durchsuchen einer Hash-Tabelle hat die einemHashSet zugrunde liegende Struktur eine Zeitkomplexität von 0 (1), während ein Array, das demArrayList zugrunde liegt, 0 (n) ist.
5. Binäre Suche
Eine andere Methode zum Durchsuchen eines Arrays istbinary search. Eine binäre Suche ist zwar sehr effizient, erfordert jedoch, dass das Array im Voraus sortiert wird.
Sortieren wir das Array und versuchen die binäre Suche:
@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, obwohl sie weniger effizient alsHashSet:ist. 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 einHashSetam besten zum Durchsuchen einer Werteliste. Wir müssen sie jedoch im Voraus erstellen und inSet. speichern
Wie immer ist der vollständige Quellcode der Beispieleover on GitHub verfügbar.