Домашняя работа для NC Java Autumn School 2021
Чураков Сергей
Рассмотрим производительность классов ArrayList, LinkedList и MyLinkedList при помощи классов ListTestClass и MyListTestClass. Запустим методы add, get, set, remove с различным числом элементов (n = 25000, 50000, 75000, 100000). Стоит отметить, что производительность методов зависит от того, работают ли они с первыми/последними элементами или элементами из середины. add у нас добавляет элементы в конец, get и set работают с каждым элементом коллекций по 1 разу, remove удаляет у нас элементы из конца списка. Например, если бы remove работал с первым/средним элементом, его производительность сильно бы упала, ведь пришлось бы каждый раз перепаковывать массив для ArrayList и ходить по элементам связанных списков.
Метод add в целом во всех списках работает быстро, ведь добавление элемента в конец списка имеет сложность O(1). Реализация MyLinkedList работает тут медленнее всех.
Метод get работает в ArrayList гораздо быстрее, чем в связанных списках, он может получить доступ к i элементу за время, практически независимое от n. Связанным спискам же нужно пройтись по всем элементам списка, лежащим между началом/концом списка и искомым элементом. Здесь почти нет разницы между реализациями LinkedList и MyLinkedList.
То же самое можно сказать про метод set.
На графике remove у ArrayList работает быстрее всех, но если бы мы делали remove из середины или начала списка, в ArrayList пришлось бы смещать все элементы между удаляемым и конечным. В связанных списках тоже бы пришлось бы проходить по всем элементам между удаляемым и начальным/конечным. remove в MyLinkedList работает быстрее, чем в LinkedList. remove в LinkedList делает дополнительные операции, чтобы помочь сборщику мусора, наверное, это и вызывает разницу в производительности.
В ArrayList быстро работают методы set и get. add и remove в ArrayList тоже работают быстро, но только потому, что мы работаем с конечным элементом. Если нам нужно часто вставлять элементы в середину/начало списка или удалять элементы из начала или конца, лучше будет воспользоваться LinkedList.
Проделаем то же самое с множествами. Рассматривать будем классы HashSet, LinkedHashSet, TreeSet. В этот раз будем тестировать методы add и remove.
Быстрее всех метод add работает в HashSet, LinkedHashSet работает немного медленнее, TreeSet работает всех медленнее.
add в HashSet должен выполняться таким образом: вычисляем hashCode вставляемого элемента, проверяем, есть ли он в bucket (поиск по списку с малым числом элементов или по дереву, когда элементов становится много), добавляем элемент в bucket. LinkedHashSet дополнительно формирует связи между вставляемым и последним вставленным элементом, поэтому работает чуть медленнее.
TreeSet может работать только с Comparable, он представляет собой сбалансированное бинарное дерево, сложность операции add будет около log(n) + балансировка.
HashSet опять работает быстрее всех, remove из LinkedHashSet работает чуть медленнее, TreeSet работает медленнее всех.
Возможно, TreeSet работает на этом примере так медленно, так как вводятся упорядоченные значения, приходится часто балансировать дерево. Если нам часто приходится вставлять/удалять элементы, лучше пользоваться HashSet или LinkedHashSet, но если нужно часто выполнять поиск, TreeSet может конкурировать с HashSet и LinkedHashSet, ведь поиск в нем должен выполняться со сложностью O(log n), тогда как в HashSet производительность поиска будет зависеть от числа коллизий. Если нам важна упорядоченность элементов, использовать надо TreeSet. Если надо запоминать порядок добавления элементов, использовать надо будет LinkedHashSet.
Проделаем то же самое с Maps. Рассматривать будем классы HashMap, LinkedHashMap, TreeMap. Рассматривать будем методы put, get, replace, remove.
Тут все очень похоже на ситуацию со списками. HashMap работает быстрее всех, LinkedHashMap чуть медленнее HashMap, TreeMap медленнее всех. Множества и мапы вообще похожи друг на друга по принципу работы. Опять же, если мы часто вставляем/удаляем элементы, лучше выбрать HashMap или LinkedHashMap. Если нам часто нужен поиск по мапе, можно выбрать или HashMap, или TreeMap, если элементы в HashMap будут неравномерно размещаться по бакетам. Если нам важна упорядоченность, лучше выбрать TreeMap. Если нужно запоминать порядок добавления элементов, используем LinkedHashMap.