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

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

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.

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.

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.