JavaRush /Курсы /Java Syntax Pro /Знакомство с коллекцией LinkedList

Знакомство с коллекцией LinkedList

Java Syntax Pro
13 уровень , 4 лекция
Открыта

1. История LinkedList

В Java есть еще один класс-коллекция, который достался Java в наследство от языка C++. Это класс LinkedList, что переводится как «Связный Список».

Внешне LinkedList — это такой же список, как и ArrayList. У класса LinkedList есть все те же методы, что и у класса ArrayList. И в принципе вы всегда можете использовать LinkedList вместо ArrayList, и все будет работать.

Так зачем же нужен еще один класс-список?

Все дело во внутреннем устройстве класса LinkedList. Вместо массива там используется двусвязный список. Что это такое, расскажем немного позже.

Но за счет другого внутреннего устройства у класса LinkedList — самая быстрая операция вставки элементов в середину списка.

В интернете часто можно найти такое сравнение классов ArrayList и LinkedList:

Операция Метод ArrayList LinkedList
Добавление элемента
add(value)
Быстро Очень быстро
Вставка элемента
add(index, value)
Медленно Очень быстро
Получение элемента
get(index)
Очень быстро Медленно
Изменение элемента
set(index, value)
Очень быстро Медленно
Удаление элемента
remove(index)
Медленно Очень быстро

Вроде бы все понятно: если нужно вставлять элементы в список часто, используйте LinkedList, если редко, то ArrayList. Однако реальность немного другая.


2. Никто не использует LinkedList

Никто не использует LinkedList.

Недавно даже сам автор кода класса LinkedList в твиттере написал пост: «Ребята, кто-нибудь вообще использует LinkedList? За 20 лет я не использовал его ни разу!».

Так в чем же дело?

Во-первых, класс ArrayList стал вставлять элементы в середину списка очень быстро. При добавлении элемента в середину списка нужно сдвинуть все элементы после нужного на 1 в сторону конца списка. Раньше это занимало время.

Но сейчас все поменялось. Все элементы массива находятся рядом в одном блоке памяти, поэтому операция по сдвигу элементов массива выполняется очень быстрой низкоуровневой командой System.arraycopy().

К тому же, сейчас у процессоров большой кэш, и обычно весь массив попадает в такой кэш, поэтому элементы массива сдвигаются даже не в памяти, а в кэше процессора. Миллион элементов легко сдвигается за одну миллисекунду.

Во-вторых, класс LinkedList быстро вставляет элементы, если вы вставляете их с помощью итератора. Если вы с помощью итератора проходитесь по списку LinkedList и постоянно вставляете новые элементы (или удаляете существующие), это действительно супербыстрая операция.

Если же вы просто в цикле добавляете элементы внутрь класса LinkedList, к каждой быстрой операции вставки добавляется медленная операция «получение элемента».

Реальность гораздо ближе к такой ситуации:

Операция Метод ArrayList LinkedList
Добавление элемента
add(value)
Быстро Очень быстро
Вставка элемента
add(index, value)
Медленно Очень медленно
Получение элемента
get(index)
Очень быстро Очень медленно
Изменение элемента
set(index, value)
Очень быстро Очень медленно
Удаление элемента
remove(index)
Медленно Очень медленно
Вставка через итератор
it.add(value)
Медленно Очень быстро
Удаление через итератор
it.remove()
Медленно Очень быстро

Почему же операция получения элемента в LinkedList такая медленная?

Ответ на этот вопрос вы узнаете, если немного ознакомитесь с устройством LinkedList


3. Устройство LinkedList

LinkedList имеет альтернативное внутреннее устройство, если сравнивать его с ArrayList. Массива для хранения элементов у него внутри нет. Вместо этого он использует структуру данных под названием двусвязный список.

Каждый элемент двусвязного списка хранит ссылки на предыдущий и следующий элемент. Это чем-то напоминает очередь, где каждый человек запоминает того, кто стоит перед ним, и того, кто стоит после него.

Вот как выглядит такой список в памяти:

Устройство LinkedList

По бокам (серый фон) переменные first и last, которые хранят ссылки на объекты типа Node.

В середине вы видите цепочку объектов (именно объектов, не переменных) типа Node. Каждый из них состоит их трех полей:

  • prev — хранит ссылку на предыдущий объект типа Node (желтый фон).
  • value — хранит значение – элемент списка (зеленый фон).
  • next — хранит ссылку на следующий объект типа Node (синий фон)

Второй объект (адрес == F24) является следующим (next) для первого и предыдущим (prev) для третьего. Желтое поле третьего объекта содержит ссылку F24 и синее поле первого объекта содержит ссылку F24.

Стрелки с первого и третьего объектов указывают на один и тот же второй объект. Поэтому более правильно было бы нарисовать стрелки так.

Устройство LinkedList 1



4. Вставка элемента в связный список

Чтобы добавить человека в такую очередь, нужно просто согласие двух соседних людей. Первый из них запоминает новичка как нового: «этот человек за мной», а второй — как нового «этот человек передо мной».

Всего-то и нужно изменить ссылки двух соседних объектов:

Вставка элемента в связный список

Мы добавили в наш список новый элемент и поменяли ссылки второго и третьего объектов. Теперь новичок, следующий за вторым и предыдущий для третьего. Ну и у самого объекта-новичка нужно прописать правильные ссылки: предыдущий объект — второй, следующий объект — третий.

Удаление еще проще. Если мы хотим удалить, допустим 100-й объект из списка, нужно просто у 99-го объекта поменять next, чтобы он указывал на 101-й объект, а у 101-го объекта поменять prev, чтобы он указывал на 99. И все.

Вот только получить 100-й объект не так просто.


5. Получение элемента списка

Чтобы получить 100-й элемент связного списка, нужно:

Получить 1-й объект: на него ссылается переменная first у объекта LinkedList. У 1-го объекта есть ссылка (поле next) на 2-й объект. С ее помощью получаем второй объект. У 2-го объекта есть ссылка на третий, и т.д.

Если нам нужно получить ссылку на 100-й объект, нам нужно последовательно пройтись по всем объектам с 1-го до 100-го. А если нам нужен миллионный элемент списка, нужно последовательно перебрать миллион объектов!

А ведь если эти объекты добавлялись в список в разное время, они находятся в разных частях памяти и вряд ли одновременно попадают в кэш процессора. А это значит, что последовательный перебор элементов связного списка — вещь не просто медленная, а очень медленная.

Такие дела.

Так зачем мы тут учим, как устроен этот медленный LinkedList?

Все дело в том, что на собеседовании вас обязательно спросят, чем LinkedList отличается от ArrayList. Обязательно.


Комментарии (595)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Katakuri Charlotte Уровень 15
5 ноября 2025
наконец-то интересные задачки
Anonymous #2845550 Уровень 15
13 октября 2025
лекция просто дно, ничего не объяснено нормально, примеры дурацкие
Zhenya Volkov Уровень 30
29 сентября 2025
помучался, но испытал неслабый кайф , когда решил обе задачи) и это при том что LinkedList вряд-ли буду использовать
invoker main Уровень 42
19 сентября 2025
2 часа на ноду и этот линкед лист, зато понял
Anonymous #3452270 Уровень 16
16 августа 2025

public String get(int index) {
        Node node = first;
        String str = null;
        for (int i = 0; i <= index; i++) {
            node = node.next;
            str = node.value;
        }
        return str;
    }
почему пишет что неправильно, если все работает
Ltn_D Уровень 27
28 сентября 2025
попробуй в качестве индекса передать отрицательное число или число больше 5.
Руслан Уровень 46
14 августа 2025
Задачки капец тяжкие
29 августа 2025
Мне хватило подумать на первой задачей чтобы решить обе. Вторая после первой крайне понятная.
Максим Уровень 34
13 августа 2025
Отвратительно написанная лекция! Вы со своими адресами ячеек excel просто ломаете восприятие. Невозможно понять связность списка с этими картинками
svxrslf Уровень 28
11 августа 2025
Может кому то поможет понять, написал комментарии более простыми словами к первой задаче, так как сам попыхтел над этой задачей public class StringLinkedList { private Node first = new Node(); // постоянный первый элемент, не имеет никакого значения private Node last = new Node(); // постоянный последний элемент, не имеет никакого значения public void add(String value) { Node node = new Node(); node.value = value; if (last.prev == null){ // если предствующий последнему элементы элемент null, лист пуст first.next = node; // тогда мы присваиваем следующему от первого элемента значение ноды last.prev = node; // и тогда мы присваиваем предыдущему элементу от последнего значение ноды }else { node.prev = last.prev; // присваивает значение перед последним элементом значением перед нодой, так как мы вставляем элементы в конец списка last.prev = node; // присваивает значение ноды значению перед последним элементом, чтобы она являлась предпоследним элементом, по той же причине, вставляем в конец списка node.prev.next = node; // Заставляет объект перед нодой считать следующим своим объектом ноду } }
25 июля 2025
Чуть мозгом не потёк на второй задаче😅
killjoyme Уровень 29
19 июля 2025
Не зря на 2 курсе неделю реализовывал LinkedList на плюсах по заданию лабораторной, хоть где-то пригодилось