Продуктивність містить () у HashSet проти ArrayList

1. Вступ

У цьому короткому посібнику ми обговоримо ефективність методу contains (), доступного в java.util. HashSet та java.util. ArrayList . Це обидві колекції для зберігання та маніпулювання об’єктами.

HashSet - це колекція для зберігання унікальних елементів. Щоб дізнатись більше про HashSet, перегляньте це посилання.

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

У нас є розширена стаття про ArrayList, доступна тут.

2. HashSet.contens ()

Внутрішньо реалізація HashSet базується на екземплярі HashMap . Метод contains () викликає HashMap.containsKey (об'єкт) .

Тут він перевіряє, чи знаходиться об’єкт на внутрішній карті чи ні. Внутрішня карта зберігає дані всередині Вузлів, відомих як сегменти. Кожному сегменту відповідає хеш-код, згенерований методом hashCode () . Таким чином, contains () насправді використовує метод hashCode () для пошуку розташування об’єкта .

Тепер визначимо складність часу пошуку. Перш ніж рухатися вперед, переконайтеся, що ви знайомі з позначенням Big-O.

В середньому, містить () з HashSet працює в O (1) часу . Отримання об'єкта розташування ковша постійна робота часу. Враховуючи можливі зіткнення, час пошуку може зрости до log (n), оскільки внутрішня структура сегмента - це TreeMap .

Це вдосконалення від Java 7, яка використовувала LinkedList для внутрішньої структури сегмента. Загалом зіткнення хеш-коду трапляються рідко. Тож ми можемо розглядати складність пошуку елементів як O (1) .

3. ArrayList.c підтримує ()

Внутрішньо ArrayList використовує метод indexOf (object), щоб перевірити, чи є об’єкт у списку . Метод indexOf (object) повторює весь масив і порівнює кожен елемент із методом equals (object) .

Повертаючись до аналізу складності, ArrayList . Метод contains () вимагає часу O (n) . Тож час, який ми витрачаємо на пошук конкретного об’єкта тут, залежить від кількості елементів, які ми маємо в масиві.

4. Бенчмарк тестування

Тепер давайте розігріємо JVM за допомогою тесту ефективності. Ми будемо використовувати продукт JMH (Java Microbenchmark Harness) OpenJDK. Щоб дізнатись більше про налаштування та виконання, перегляньте наш корисний посібник.

Для початку давайте створимо простий клас 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 з Працівника об'єктів:

public class Employee { private Long id; private String name; // constructor and getter setters go here }

Ми додаємо екземпляр worker = new Employee (100L, “Harry”) як останні елементи до обох колекцій. Тож ми перевіряємо час пошуку об’єкта працівника на найгірший можливий випадок.

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

Після цього ми додаємо наші тестові методи до класу CollectionsBenchmark :

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

Тут ми перевіряємо чи employeeList містить персонал об'єкта.

Так само ми маємо знайомий тест для workerSet :

@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 нс , тоді як testHashSet працює швидше, в середньому 9,456 нс .

Тепер давайте збільшимо кількість елементів у нашому тесті та запустімо його для ітерацій = 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

Тут також вміст (() у HashSet має величезну перевагу в продуктивності перед ArrayList .

5. Висновок

Це швидко рецензія пояснює продуктивність містить () метод в HashSet і ArrayList колекцій. За допомогою бенчмаркінгу JMH ми представили ефективність contains () для кожного типу колекції.

На закінчення, ми можемо дізнатися, що містить () метод працює швидше в HashSet по порівнянні з ArrayList .

Як завжди, повний код цієї статті закінчений на проекті GitHub.