Controleer of een Java-array een waarde bevat

1. Overzicht

In dit artikel zullen we verschillende manieren bekijken om in een array naar een opgegeven waarde te zoeken.

We zullen ook vergelijken hoe deze presteren met JMH (de Java Microbenchmark Harness) om te bepalen welke methode het beste werkt.

2. Installatie

Voor onze voorbeelden gebruiken we een array die willekeurig gegenereerde Snaren voor elke test:

String [] seedArray (int lengte) {String [] strings = nieuwe String [lengte]; Willekeurige waarde = nieuwe Willekeurig (); for (int i = 0; i <length; i ++) {strings [i] = String.valueOf (value.nextInt ()); } retourstrings; }

Om de array in elke benchmark opnieuw te gebruiken, declareren we een inner class om de array en het aantal te bevatten, zodat we de reikwijdte ervan voor JMH kunnen aangeven:

@State (Scope.Benchmark) openbare statische klasse SearchData {statische int count = 1000; statische String [] strings = seedArray (1000); } 

3. Basiszoekopdracht

Drie veelgebruikte methoden voor het doorzoeken van een array zijn de Lijst, een Set, of met een lus dat elk lid onderzoekt totdat het een match vindt.

Laten we beginnen met drie methoden die elk algoritme implementeren:

boolean searchList (String [] strings, String searchString) {return Arrays.asList (SearchData.strings) .contains (searchString); } boolean searchSet (String [] strings, String searchString) {Set stringSet = nieuwe 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; }

We zullen deze klasse-annotaties gebruiken om JMH te vertellen de gemiddelde tijd in microseconden uit te voeren en vijf opwarmings-iteraties uit te voeren om ervoor te zorgen dat onze tests betrouwbaar zijn:

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

En voer elke test in een lus uit:

@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 openbare leegte searchArrayAllocNewSet () {voor (int i = 0; i <SearchData.count; i ++) {searchSet (SearchData.strings, "S"); }} 

Als we voor elke methode 1000 zoekopdrachten uitvoeren, zien onze resultaten er ongeveer zo uit:

SearchArrayTest.searchArrayAllocNewList gemiddelde 20937.851 ± 14.226 us / op SearchArrayTest.searchArrayAllocNewSet gemiddelde 20 14309.122 ± 193.844 us / op SearchArrayTest.searchArrayLoop gemiddelde 20758.060 ± 9.433 us / op 

Het zoeken naar een lus is efficiënter dan andere. Maar dit komt in ieder geval gedeeltelijk door de manier waarop we collecties gebruiken.

We creëren een nieuwe Lijst instantie met elke oproep naar zoeklijst () en een nieuwe Lijst en een nieuwe HashSet bij elke oproep naar searchSet (). Het maken van deze objecten zorgt voor extra kosten die het doorlopen van de array niet doet.

4. Efficiënter zoeken

Wat gebeurt er als we enkele exemplaren maken van Lijst en Set en ze vervolgens voor elke zoekopdracht opnieuw gebruiken?

Laten we het proberen:

openbare ongeldige searchArrayReuseList () {List asList = Arrays.asList (SearchData.strings); for (int i = 0; i <SearchData.count; i ++) {asList.contains ("T"); }} openbare leegte searchArrayReuseSet () {Set asSet = nieuwe HashSet (Arrays.asList (SearchData.strings)); for (int i = 0; i <SearchData.count; i ++) {asSet.contains ("T"); }} 

We zullen deze methoden uitvoeren met dezelfde JMH-annotaties als hierboven, en de resultaten voor de eenvoudige lus opnemen ter vergelijking.

We zien heel verschillende resultaten:

SearchArrayTest.searchArrayLoop gemiddelde 20758.060 ± 9.433 us / op SearchArrayTest.searchArrayReuseList gemiddelde 20837.265 ± 11.283 us / op SearchArrayTest.searchArrayReuseSet gemiddelde 20 14.030 ± 0.197 us / op 

Tijdens het zoeken in het Lijst is iets sneller dan voorheen, Set zakt naar minder dan 1 procent van de tijd die nodig is voor de lus!

Nu we de tijd die nodig is voor het maken van nieuwe collecties uit elke zoekopdracht hebben verwijderd, zijn deze resultaten logisch.

Zoeken in een hashtabel, de onderliggende structuur van een HashSet, heeft een tijdcomplexiteit van 0 (1), terwijl een array die ten grondslag ligt aan de ArrayList is 0 (n).

5. Binair zoeken

Een andere methode om in een array te zoeken, is een binaire zoekopdracht. Hoewel het erg efficiënt is, vereist een binaire zoekopdracht dat de array van tevoren wordt gesorteerd.

Laten we de array sorteren en de binaire zoekopdracht proberen:

@Benchmark openbare leegte searchArrayBinarySearch () {Arrays.sort (SearchData.strings); voor (int i = 0; i <SearchData.count; i ++) {Arrays.binarySearch (SearchData.strings, "T"); }} 
SearchArrayTest.searchArrayBinarySearch gem. 20 26,527 ± 0,376 us / op 

Binair zoeken is erg snel, hoewel minder efficiënt dan het HashSet: de slechtste prestatie voor een binaire zoekopdracht is 0 (log n), wat de prestatie tussen die van een arrayzoekopdracht en een hashtabel plaatst.

6. Conclusie

We hebben verschillende methoden gezien om door een array te zoeken.

Op basis van onze resultaten, a HashSet werkt het beste voor het doorzoeken van een lijst met waarden. We moeten ze echter van tevoren maken en opslaan in het Set.

Zoals altijd is de volledige broncode van de voorbeelden beschikbaar op GitHub.