1. История LinkedList
В Java есть еще один класс-коллекция, который достался Java в наследство от языка C++. Это класс LinkedList, что переводится как «Связный Список».
Внешне LinkedList — это такой же список, как и ArrayList. У класса LinkedList есть все те же методы, что и у класса ArrayList. И в принципе вы всегда можете использовать LinkedList вместо ArrayList, и все будет работать.
Так зачем же нужен еще один класс-список?
Все дело во внутреннем устройстве класса LinkedList. Вместо массива там используется двусвязный список. Что это такое, расскажем немного позже.
Но за счет другого внутреннего устройства у класса LinkedList — самая быстрая операция вставки элементов в середину списка.
В интернете часто можно найти такое сравнение классов ArrayList и LinkedList:
| Операция | Метод | ArrayList | LinkedList |
|---|---|---|---|
| Добавление элемента | |
Быстро | Очень быстро |
| Вставка элемента | |
Медленно | Очень быстро |
| Получение элемента | |
Очень быстро | Медленно |
| Изменение элемента | |
Очень быстро | Медленно |
| Удаление элемента | |
Медленно | Очень быстро |
Вроде бы все понятно: если нужно вставлять элементы в список часто, используйте LinkedList, если редко, то ArrayList. Однако реальность немного другая.
2. Никто не использует LinkedList
Никто не использует LinkedList.
Недавно даже сам автор кода класса LinkedList в твиттере написал пост: «Ребята, кто-нибудь вообще использует LinkedList? За 20 лет я не использовал его ни разу!».
Так в чем же дело?
Во-первых, класс ArrayList стал вставлять элементы в середину списка очень быстро. При добавлении элемента в середину списка нужно сдвинуть все элементы после нужного на 1 в сторону конца списка. Раньше это занимало время.
Но сейчас все поменялось. Все элементы массива находятся рядом в одном блоке памяти, поэтому операция по сдвигу элементов массива выполняется очень быстрой низкоуровневой командой System.arraycopy().
К тому же, сейчас у процессоров большой кэш, и обычно весь массив попадает в такой кэш, поэтому элементы массива сдвигаются даже не в памяти, а в кэше процессора. Миллион элементов легко сдвигается за одну миллисекунду.
Во-вторых, класс LinkedList быстро вставляет элементы, если вы вставляете их с помощью итератора. Если вы с помощью итератора проходитесь по списку LinkedList и постоянно вставляете новые элементы (или удаляете существующие), это действительно супербыстрая операция.
Если же вы просто в цикле добавляете элементы внутрь класса LinkedList, к каждой быстрой операции вставки добавляется медленная операция «получение элемента».
Реальность гораздо ближе к такой ситуации:
| Операция | Метод | ArrayList | LinkedList |
|---|---|---|---|
| Добавление элемента | |
Быстро | Очень быстро |
| Вставка элемента | |
Медленно | Очень медленно |
| Получение элемента | |
Очень быстро | Очень медленно |
| Изменение элемента | |
Очень быстро | Очень медленно |
| Удаление элемента | |
Медленно | Очень медленно |
| Вставка через итератор | |
Медленно | Очень быстро |
| Удаление через итератор | |
Медленно | Очень быстро |
Почему же операция получения элемента в LinkedList такая медленная?
Ответ на этот вопрос вы узнаете, если немного ознакомитесь с устройством LinkedList
3. Устройство LinkedList
LinkedList имеет альтернативное внутреннее устройство, если сравнивать его с ArrayList. Массива для хранения элементов у него внутри нет. Вместо этого он использует структуру данных под названием двусвязный список.
Каждый элемент двусвязного списка хранит ссылки на предыдущий и следующий элемент. Это чем-то напоминает очередь, где каждый человек запоминает того, кто стоит перед ним, и того, кто стоит после него.
Вот как выглядит такой список в памяти:

По бокам (серый фон) переменные first и last, которые хранят ссылки на объекты типа Node.
В середине вы видите цепочку объектов (именно объектов, не переменных) типа Node. Каждый из них состоит их трех полей:
prev— хранит ссылку на предыдущий объект типаNode(желтый фон).value— хранит значение – элемент списка (зеленый фон).next— хранит ссылку на следующий объект типаNode(синий фон)
Второй объект (адрес == F24) является следующим (next) для первого и предыдущим (prev) для третьего. Желтое поле третьего объекта содержит ссылку F24 и синее поле первого объекта содержит ссылку F24.
Стрелки с первого и третьего объектов указывают на один и тот же второй объект. Поэтому более правильно было бы нарисовать стрелки так.

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

Мы добавили в наш список новый элемент и поменяли ссылки второго и третьего объектов. Теперь новичок, следующий за вторым и предыдущий для третьего. Ну и у самого объекта-новичка нужно прописать правильные ссылки: предыдущий объект — второй, следующий объект — третий.
Удаление еще проще. Если мы хотим удалить, допустим 100-й объект из списка, нужно просто у 99-го объекта поменять next, чтобы он указывал на 101-й объект, а у 101-го объекта поменять prev, чтобы он указывал на 99. И все.
Вот только получить 100-й объект не так просто.
5. Получение элемента списка
Чтобы получить 100-й элемент связного списка, нужно:
Получить 1-й объект: на него ссылается переменная first у объекта LinkedList. У 1-го объекта есть ссылка (поле next) на 2-й объект. С ее помощью получаем второй объект. У 2-го объекта есть ссылка на третий, и т.д.
Если нам нужно получить ссылку на 100-й объект, нам нужно последовательно пройтись по всем объектам с 1-го до 100-го. А если нам нужен миллионный элемент списка, нужно последовательно перебрать миллион объектов!
А ведь если эти объекты добавлялись в список в разное время, они находятся в разных частях памяти и вряд ли одновременно попадают в кэш процессора. А это значит, что последовательный перебор элементов связного списка — вещь не просто медленная, а очень медленная.
Такие дела.
Так зачем мы тут учим, как устроен этот медленный LinkedList?
Все дело в том, что на собеседовании вас обязательно спросят, чем LinkedList отличается от ArrayList. Обязательно.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ