Какую реализацию List в Java выбрать для частого удаления элементов из середины списка?

Ответ

Для частого удаления элементов из произвольных позиций (не только начала/конца) в однопоточном приложении лучше использовать LinkedList.

Почему LinkedList?

  • Сложность O(1): После нахождения нужного узла (что само по себе O(n)), операция удаления в LinkedList выполняется за константное время. Нужно лишь перенаправить ссылки у соседних узлов.
  • Нет сдвига данных: В отличие от массива, не требуется перемещение последующих элементов.

Почему не ArrayList? В ArrayList удаление элемента из середины требует сдвига всех последующих элементов на одну позицию, что является операцией O(n).

Пример:

// LinkedList - эффективное удаление из середины
List<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B"); // Узел для удаления
linkedList.add("C");
linkedList.remove(1); // Быстро: меняются ссылки B->A и B->C

// ArrayList - неэффективное удаление из середины
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add("B"); // Элемент для удаления
arrayList.add("C");
arrayList.add("D");
arrayList.remove(1); // Медленно: элементы "C" и "D" сдвигаются влево

Компромисс: LinkedList проигрывает в скорости доступа по индексу (O(n)). Если нужны и частые удаления, и частый доступ по индексу, возможно, стоит рассмотреть другую структуру данных (например, TreeSet или CopyOnWriteArrayList для многопоточности).

Ответ 18+ 🔞

А, слушай, вот тут у нас классический спор, который у каждого джуна мозги выносит напрочь. Смотри, если тебе надо постоянно выковыривать элементы из середины списка, как занозы из жопы, то твой выбор — LinkedList, и вот почему, блядь.

А че он такой хороший-то?

  • Сложность O(1), ёпта! Ну, если ты уже нашёл этот самый узел (а поиск, да, O(n), с этим не поспоришь), то само удаление — это просто перекинуть парочку ссылок у соседних узлов. Раз — и готово. Никакого геморроя.
  • Никакого сдвига, нахуй! Тут не массив, где после удаления одного элемента надо всю ораву остальных подвигать, как нищих у метро. Тут чистая работа с указателями.

А че ArrayList тогда говно? А потому что в ArrayList удаление из середины — это пиздец какой O(n). Представь: выдернул кирпич из середины стены, и теперь надо вручную всю хуйню, что была справа, на один шаг левее перетащить. Заебёшься. Именно это он и делает.

Смотри, как это выглядит в коде, на примерах:

// LinkedList — удаляем по-быстрому, как будто так и надо
List<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B"); // Вот эту сволочь будем удалять
linkedList.add("C");
linkedList.remove(1); // Щёлк — и ссылки у A и C пересоединились. B сдохла.

// ArrayList — тут начинается адский сдвиг
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add("B"); // И эту же сволочь удаляем
arrayList.add("C");
arrayList.add("D");
arrayList.remove(1); // А вот тут пошла жара: C ползёт на место B, D ползёт на место C. Все охуели.

Но, блядь, есть нюанс, как говорится! LinkedList — это не волшебная таблетка. Доступ по индексу у него — та ещё песня, тоже O(n), потому что приходится с начала ползти, как увалень. Если тебе нужно и часто тыкать в элементы по их номеру, и часто их выдирать, то тут, дружок, надо думать. Может, тебе TreeSet подойдёт, или, если потоки шалят, CopyOnWriteArrayList. Но это уже совсем другая история, ебать её в сраку.