Как стать автором
Обновить

Вставка в середину: ArrayList vs LinkedList

Вставка в середину где быстрее у ArrayList или LinkedList? Избитый вопрос, ответ на который часто можно услышать один из двух:

  1. “LinkedList. Там мы только ссылки переназначаем, а в ArrayList нужно сместить половину коллекции вправо плюс время от времени будем ловить потерю перфоманса на расширение емкости”.
  2. “ArrayList. Ведь копирование происходит нативным методом, который довольно быстрый, в то время как LinkedList должен еще добраться до середины, перебирая каждый элемент, а это уже линейная асимптотическая сложность, в то время как у ArrayList это значение константа”. Также вспоминают, что емкость можно задать изначально больше, что снизит потери по производительности (дефолтная емкость ArrayList равна 10).

Попробуем разобраться кто прав


Oт теории к делу. Тестировать будем при помощи JMH. К слову, это весьма полезный бенчмарк для подобного юнит тестирования от Алексея Шипелева.

Исходные данные: коллекции ArrayList и LinkedList одинакового размера (10, 100, 1000, 10_000, 100_000, 1_000_000 элементов). Замерять будем среднюю скорость вставки одного элемента по индексу в середину. Замер в наносекундах. Тесты заключаются в прогоне 10 итераций, перед которыми также запускаем 10 итераций для прогрева JVM (результаты прогрева не учитываются). В течении каждой итерации на протяжении 10 секунд происходит вставка в середину. Важный момент: для чистоты эксперимента каждая последующая вставка возвращает коллекцию в исходное состояние. Для точности всю процедуру повторяем.

Конфигурация JMH
image

Исходный код
@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@Measurement(time = 10)
@Warmup(time = 10)
public class InsertInMiddle {
    @Param({"10", "100", "1000", "10000", "100000", "1000000"})
    private int size;
    private Byte byteVal = 7;
    private List<Byte> arrayList = new ArrayList<>();
    private List<Byte> linkedList = new LinkedList<>();

    @Setup(Level.Invocation)
    public void doSetup() {
        for (int i = 0; i < size; i++) {
            arrayList.add(byteVal);
        }
        for (int i = 0; i < size; i++) {
            linkedList.add(byteVal);
        }
    }

    @TearDown(Level.Invocation)
    public void doTearDown() {
        arrayList = new ArrayList<>();
        linkedList = new LinkedList<>();
    }

    @Benchmark
    public void testArrayList() {
        arrayList.add(size / 2, byteVal);
    }

    @Benchmark
    public void testLinkedList() {
        linkedList.add(size / 2, byteVal);
    }

    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder().include(com.epam.InsertInMiddle.class.getSimpleName())
                .warmupIterations(10)
                .measurementIterations(10)
                .jvmArgs("-Xms10000m", "-Xmx10000m", "-XX:MaxDirectMemorySize=2500M")
                .forks(2)
                .build();
        new Runner(options).run();
    }
}


В результате выполнения тестов получаем


image

О чем говорят результаты


ArrayList уступил только при вставке в коллекцию размером 10 элементов. Выигрыш у LinkedList в этом случае вполне оправдан, так как элементов мало и следовательно пробежаться до середины и переназначить ссылки кажется не затратным делом, в то время как ArrayList требует сначала увеличить емкость (11-ый элемент уже не вмещается в дефолтную емкость) и затем копировать половину коллекции от середины вправо. В остальных рассматриваемых случаях перевес на стороне ArrayList.

Надеюсь данные наблюдения показались интересными и возможно сделают ваш ответ на собеседовании более развернутым. Спасибо за внимание!
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.