Verifique se uma matriz Java contém um valor
1. Visão geral
Neste artigo, veremos diferentes maneiras de pesquisar uma matriz para um valor especificado.
Também compararemos seu desempenho usandoJMH (o Java Microbenchmark Harness) para determinar qual método funciona melhor.
2. Configuração
Para nossos exemplos, usaremos uma matriz que contémStrings gerado aleatoriamente para cada teste:
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;
}
Para reutilizar a matriz em cada benchmark, declararemos uma classe interna para conter a matriz e a contagem, para que possamos declarar seu escopo para JMH:
@State(Scope.Benchmark)
public static class SearchData {
static int count = 1000;
static String[] strings = seedArray(1000);
}
3. Pesquisa básica
Three commonly used methods for searching an array are as a List, a Set, or with a loop que examina cada membro até encontrar uma correspondência.
Vamos começar com três métodos que implementam cada algoritmo:
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;
}
Usaremos essas anotações de classe para dizer ao JMH para gerar o tempo médio em microssegundos e executar por cinco iterações de aquecimento para garantir que nossos testes sejam confiáveis:
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
E execute cada teste em um loop:
@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");
}
}
Quando executamos com 1000 pesquisas para cada método, nossos resultados são mais ou menos assim:
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. Mas isso é pelo menos em parte por causa de como estamos usando coleções.
Estamos criando uma nova instânciaList com cada chamada parasearchList() e um novoListe um novoHashSet com cada chamada parasearchSet(). Criar esses objetos cria um custo adicional que o loop pela matriz não cria.
4. Pesquisa Mais Eficiente
O que acontece quando criamos instâncias únicas deList eSete as reutilizamos para cada pesquisa?
Vamos tentar:
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");
}
}
Executaremos esses métodos com as mesmas anotações JMH acima e incluiremos os resultados do loop simples para comparação.
Vemos resultados muito diferentes:
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
Embora a pesquisa deList seja ligeiramente mais rápida do que antes,Set cai para menos de 1 por cento do tempo necessário para o loop!
Agora que removemos o tempo necessário para criar novas coleções de cada pesquisa, esses resultados fazem sentido.
A pesquisa em uma tabela hash, a estrutura subjacente aHashSet, tem uma complexidade de tempo de 0 (1), enquanto uma matriz, que está por trás deArrayList, é 0 (n).
5. Pesquisa binária
Outro método para pesquisar uma matriz ébinary search. Embora muito eficiente, uma pesquisa binária exige que a matriz seja classificada com antecedência.
Vamos classificar a matriz e tentar a pesquisa binária:
@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
A pesquisa binária é muito rápida, embora menos eficiente que oHashSet:, o desempenho do pior caso para uma pesquisa binária é 0 (log n), o que coloca seu desempenho entre o de uma pesquisa de array e uma tabela hash.
6. Conclusão
Vimos vários métodos de pesquisa em uma matriz.
Com base em nossos resultados, aHashSet funciona melhor para pesquisar em uma lista de valores. No entanto, precisamos criá-los com antecedência e armazená-los noSet.
Como sempre, o código-fonte completo dos exemplos está disponívelover on GitHub.