На какой структуре данных основана реализация LinkedList в Java?

Ответ

Двусвязный список. Каждый элемент (узел) хранит:

  • E item — данные.
  • Node<E> next — ссылку на следующий узел.
  • Node<E> prev — ссылку на предыдущий узел.

Внутренний класс узла:

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
Сложность основных операций: Операция Сложность Примечание
Вставка/удаление в начале/конце O(1) Известны ссылки на head и tail.
Вставка/удаление по индексу O(n) Требуется линейный обход до позиции.
Получение элемента по индексу O(n) Линейный поиск.
Поиск по значению O(n) Линейный поиск.

Память: Занимает больше, чем ArrayList, из-за хранения двух дополнительных ссылок на каждый элемент.

Ответ 18+ 🔞

Давай разберём эту штуку, как будто я тебе на кухне объясняю, водку при этом не предлагаю, но атмосфера та ещё.

Представь себе цепочку из людей в очереди за колбасой, только каждый — не просто мудак, а мудак с двумя телефонами. В одном у него номер того, кто перед ним, в другом — кто сзади. Это и есть двусвязный список, ёпта. Каждый узел — это такой вот хитрожопый чувак.

Вот смотри, как он внутри устроен, этот узел-мудак:

private static class Node<E> {
    E item; // Сама колбаса, которую он держит. Данные, короче.
    Node<E> next; // Телефон, по которому звонит тому, кто сзади него. Ссылка на следующий узел.
    Node<E> prev; // Телефон, по которому звонит тому, кто перед ним. Ссылка на предыдущий узел.

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

Он, сука, такой: «Я стою между Васей и Петей. Вася — это prev, Петя — next. А в руках у меня — item, моя драгоценная палка колбасы».

А теперь про скорость, тут весело. Сложность операций, блядь:

  • Впихнуть или выкинуть кого-то в начало/конец очереди (вставка/удаление в начале/конце): O(1). Вообще быстро, потому что мы знаем, кто там первый и последний (head и tail). Подошёл, взял за шкирку, переставил — готово. Никого по всей очереди бегать не надо.
  • Впихнуть или выкинуть кого-то по номеру в очереди (по индексу): O(n). Вот тут пиздец начинается. Ты говоришь: «Мне нужен 102-й человек». И пошёл ты, блядь, с начала считать: раз, два, три... девяносто девять, сто, сто один, сто два — вот он, нашёлся! Линейный обход, хуле.
  • Посмотреть, что за колбаса у 102-го (получение по индексу): O(n). Та же хуйня. Беги и считай снова.
  • Найти человека по виду колбасы (поиск по значению): O(n). Вообще ахтунг. Ты идёшь и каждому в руки смотришь: «У тебя «Докторская»? Нет? А у тебя?» Пока не найдёшь или всю очередь не пройдёшь.

Память: А жрёт эта конструкция, ядрёна вошь, больше, чем обычный динамический массив (ArrayList). Потому что каждый наш мудак-узел тащит на себе не только колбасу (item), но и два целых телефона (next и prev) — две дополнительные ссылки. За удобство двустороннего движения (вперёд-назад) платим местом.

Короче, если тебе надо часто тыкать в начало и конец — это твой выбор, огонь. Если же ты планируешь постоянно прыгать по индексам и смотреть, что там внутри — готовься к долгой пробежке, это не твой инструмент, дружок. Выбирай с умом, а то будешь как Герасим с Муму — вроде сила есть, а нихуя не можешь объяснить.