Производительность содержит () в HashSet против ArrayList

Производительность содержит () в HashSet против ArrayList

1. Вступление

В этом кратком руководствеwe’re going to discuss the performance of the contains() method available in java.util.HashSet and java.util.ArrayList. Они обе коллекции для хранения и манипулирования объектами.

HashSet - это коллекция для хранения уникальных элементов. Чтобы узнать больше оHashSet,, ознакомьтесь сthis link.

ArrayList - популярная реализация интерфейсаjava.util.List.

У нас есть расширенная статья о доступномArrayListhere.

2. HashSet.contains()с

Внутренне реализацияHashSet основана на синстанцииHashMap . Методcontains() вызываетHashMap.containsKey(object).

Здесь он проверяет, находится лиobject на внутренней карте или нет. Внутренняя карта хранит данные внутри узлов, известных как сегменты. Каждая корзина соответствует хэш-коду, сгенерированному с помощью методаhashCode() . Итак,contains() на самом деле использует методhashCode() , чтобы найти расположениеobject’s .

Теперь давайте определим временную сложность поиска. Прежде чем двигаться дальше, убедитесь, что вы знакомы сBig-O notation.

В среднем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.

Это улучшение по сравнению с Java 7, в которой для внутренней структуры сегмента использовалсяLinkedList. В общем, коллизии хеш-кода встречаются редко. Таким образом, мы можем рассматривать сложность поиска элементов какO(1).

3. ArrayList.cс ontains ()

ВнутреннеArrayList uses the indexOf(object) method to check if the object is in the list. МетодindexOf(object) выполняет итерацию по всему массиву и сравнивает каждый элемент с методомequals(object).

Возвращаясь к анализу сложности, методArrayList.contains() требуетO(n) времени. So the time we spend to find a specific object here depends on the number of items we have in the array.

4. Тестирование производительности

Теперь давайте разогреем JVM с помощью теста производительности. Мы будем использовать продукт OpenJDK JMH (Java Microbenchmark Harness). Чтобы узнать больше о настройке и выполнении, ознакомьтесь с нашимuseful guide.

Для начала создадим простой классCollectionsBenchmark:

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

Здесь мы создаем и инициализируемHashSet иArrayList объектовEmployee:

public class Employee {

    private Long id;
    private String name;

    // constructor and getter setters go here
}

Мы добавляемemployee = new Employee(100L, “Harry”) instance в качестве последних элементов в обе коллекции. Итак, мы проверяем время поиска объектаemployee для наихудшего случая.

@OutputTimeUnit(TimeUnit.NANOSECONDS) указывает, что нам нужны результаты в наносекундах. Количество итераций@Warmup по умолчанию в нашем случае равно 5. Для@BenchmarkMode установлено значениеMode.AverageTime, что означает, что нас интересует вычисление среднего времени работы. Для первого выполнения мы помещаем элементыiterations = 1000 в наши коллекции.

После этого мы добавляем наши тестовые методы в классCollectionsBenchmark:

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

Здесь мы проверяем, содержит лиemployeeList объектemployee.

Точно так же у нас есть знакомый тест дляemployeeSet:

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

Наконец, мы можем запустить тест:

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

Вот результаты:

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

Мы ясно видим, что методtestArrayList имеет средний результат поиска4035.646 ns, в то время какtestHashSet работает быстрее с9.456 ns в среднем.

Теперь давайте увеличим количество элементов в нашем тесте и запустим его для итераций = 10.000 элементов:

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

Здесь такжеcontains() вHashSet имеет огромное преимущество в производительности по сравнению сArrayList.

5. Заключение

В этой быстрой записи объясняется производительность методаcontains() для коллекцийHashSet иArrayList. С помощью тестирования JMH мы представили производительностьcontains() для каждого типа коллекции.

В заключение мы можем узнать, чтоthe contains() method works faster in HashSet compared to an ArrayList.

Как обычно, полный код этой статьи -over on GitHub project.