Desempenho de contains () em um HashSet vs ArrayList

Desempenho de contains () em um HashSet vs ArrayList

1. Introdução

Neste guia rápido,we’re going to discuss the performance of the contains() method available in java.util.HashSet and java.util.ArrayList. Ambas são coleções para armazenar e manipular objetos.

HashSet é uma coleção para armazenar elementos únicos. Para saber mais sobreHashSet,, verifiquethis link.

ArrayList é uma implementação popular da interfacejava.util.List.

Temos um artigo estendido sobreArrayList disponívelhere.

2. HashSet.contains()

Internamente, a implementação deHashSet é baseada em uma instânciaHashMap . O métodocontains() chamaHashMap.containsKey(object).

Aqui, ele está verificando se oobject está no mapa interno ou não. O mapa interno armazena dados dentro dos Nós, conhecidos como buckets. Cada depósito corresponde a um código hash gerado comhashCode() method. Portanto,contains() está, na verdade, usando o métodohashCode() method para encontrar o deslocamentoobject’s .

Agora vamos determinar a complexidade do tempo de pesquisa. Antes de prosseguir, certifique-se de estar familiarizado comBig-O notation.

Em média,the contains() of HashSet runs in O(1) time. Getting the object’s bucket location is a constant time operation. Taking into account possible collisions, the lookup time may rise to log(n) because the internal bucket structure is a TreeMap.

Esta é uma melhoria do Java 7 que usavaLinkedList para a estrutura interna do balde. Em geral, colisões de código de hash são raras. Portanto, podemos considerar a complexidade da pesquisa de elementos comoO(1).

3. ArrayList.c ontains ()

Internamente,ArrayList uses the indexOf(object) method to check if the object is in the list. O métodoindexOf(object) itera a matriz inteira e compara cada elemento com o métodoequals(object).

Voltando à análise de complexidade, o métodoArrayList.contains() requerO(n) tempo. So the time we spend to find a specific object here depends on the number of items we have in the array.

4. Teste de referência

Agora, vamos aquecer a JVM com o teste de benchmark de desempenho. Usaremos o produto JMH (Java Microbenchmark Harness) OpenJDK. Para saber mais sobre configuração e execução, verifique nossouseful guide.

Para começar, vamos criar uma classeCollectionsBenchmark simples:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5)
public class CollectionsBenchmark {

    @State(Scope.Thread)
    public static class MyState {
        private Set employeeSet = new HashSet<>();
        private List employeeList = new ArrayList<>();

        private long iterations = 1000;

        private Employee employee = new Employee(100L, "Harry");

        @Setup(Level.Trial)
        public void setUp() {

            for (long i = 0; i < iterations; i++) {
                employeeSet.add(new Employee(i, "John"));
                employeeList.add(new Employee(i, "John"));
            }

            employeeList.add(employee);
            employeeSet.add(employee);
        }
    }
}

Aqui, criamos e inicializamosHashSet e umArrayList de objetosEmployee:

public class Employee {

    private Long id;
    private String name;

    // constructor and getter setters go here
}

Adicionamosemployee = new Employee(100L, “Harry”) instance como os últimos elementos para ambas as coleções. Portanto, testamos o tempo de pesquisa do objetoemployee para o pior caso possível.

@OutputTimeUnit(TimeUnit.NANOSECONDS) indica que queremos os resultados em nanossegundos. O número de iterações padrão@Warmup é 5 em nosso caso. O@BenchmarkMode é definido comoMode.AverageTime, o que significa que estamos interessados ​​em calcular um tempo médio de execução. Para a primeira execução, colocamositerations = 1000 itens em nossas coleções.

Depois, adicionamos nossos métodos de benchmark à classeCollectionsBenchmark:

@Benchmark
public boolean testArrayList(MyState state) {
    return state.employeeList.contains(state.employee);
}

Aqui verificamos seemployeeList contém o objetoemployee.

Da mesma forma, temos o teste familiar paraemployeeSet:

@Benchmark
public boolean testHashSet(MyState state) {
    return state.employeeSet.contains(state.employee);
}

Finalmente, podemos executar o teste:

public static void main(String[] args) throws Exception {
    Options options = new OptionsBuilder()
      .include(CollectionsBenchmark.class.getSimpleName())
      .forks(1).build();
    new Runner(options).run();
}

Aqui estão os resultados:

Benchmark                           Mode  Cnt     Score     Error  Units
CollectionsBenchmark.testArrayList  avgt   20  4035.646 ± 598.541  ns/op
CollectionsBenchmark.testHashSet    avgt   20     9.456 ±   0.729  ns/op

Podemos ver claramente que o métodotestArrayList tem pontuação média de pesquisa4035.646 ns, enquantotestHashSet tem desempenho mais rápido com9.456 ns em média.

Agora, vamos aumentar a contagem de elementos em nosso teste e executá-lo para iterações = 10.000 itens:

Benchmark                           Mode  Cnt      Score       Error  Units
CollectionsBenchmark.testArrayList  avgt   20  57499.620 ± 11388.645  ns/op
CollectionsBenchmark.testHashSet    avgt   20     11.802 ±     1.164  ns/op

Aqui também,contains() emHashSet tem uma grande vantagem de desempenho sobreArrayList.

5. Conclusão

Este rápido artigo explica o desempenho do métodocontains() das coleçõesHashSeteArrayList. Com a ajuda do benchmarking JMH, apresentamos o desempenho decontains() para cada tipo de coleção.

Como conclusão, podemos aprender quethe contains() method works faster in HashSet compared to an ArrayList.

Como de costume, o código completo para este artigo éover on GitHub project.