← Назад к вопросам
В чем разница между ArrayDeque и LinkedList?
2.0 Middle🔥 161 комментариев
#Коллекции
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
В чем разница между ArrayDeque и LinkedList
Это популярный вопрос на Java интервью. Оба класса реализуют Deque (Double Ended Queue), но имеют существенные различия в производительности и использовании памяти.
Таблица сравнения
| Характеристика | ArrayDeque | LinkedList |
|---|---|---|
| Внутренняя структура | Динамический массив | Двусвязный список |
| Доступ по индексу get(i) | O(1) | O(n) |
| Добавление в начало addFirst() | O(1) амортиз. | O(1) |
| Добавление в конец addLast() | O(1) амортиз. | O(1) |
| Удаление с начала removeFirst() | O(1) | O(1) |
| Удаление с конца removeLast() | O(1) | O(1) |
| Потребление памяти | Меньше | Больше (указатели) |
| Thread-safe | Нет | Нет |
| Может быть null | Нет | Да |
| Кешируемость | Лучше (массив) | Хуже (разбросаны в памяти) |
Структуры данных
ArrayDeque
Это динамический циклический буфер на базе массива:
Визуализация ArrayDeque:
head tail
| |
[A][B][C][D][E][ ][ ]
0 1 2 3 4 5 6
Когда добавляем в начало или конец:
- Если конец не полон: O(1)
- Если конец полон: расширяем массив (амортизировано O(1))
Циклический буфер:
head = 4, tail = 2 (tail обогнул начало)
[ ][B][C][ ][E][F][A]
0 1 2 3 4 5 6
Внутренняя реализация:
public class ArrayDeque<E> extends AbstractCollection<E>
implements Deque<E>, Cloneable, Serializable {
transient Object[] elements; // Циклический буфер
transient int head; // Индекс первого элемента
transient int tail; // Индекс после последнего
}
LinkedList
Это двусвязный список:
Визуализация LinkedList:
null <-> [Node: A] <-> [Node: B] <-> [Node: C] <-> null
head tail
Каждый Node:
public class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
Внутренняя реализация:
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable {
transient int size = 0;
transient Node<E> first; // Ссылка на первый элемент
transient Node<E> last; // Ссылка на последний элемент
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
}
Различия в производительности
1. Доступ по индексу
// ArrayDeque: O(1)
ArrayDeque<Integer> aq = new ArrayDeque<>();
for (int i = 0; i < 1000; i++) aq.add(i);
int element = aq.get(500); // Быстро!
// LinkedList: O(n)
LinkedList<Integer> ll = new LinkedList<>();
for (int i = 0; i < 1000; i++) ll.add(i);
int element = ll.get(500); // Медленно (проходит 500 узлов)
2. Итерация
// ArrayDeque: быстро (cache-friendly)
for (int x : deque) {
// Элементы рядом в памяти - L1/L2 cache hit
}
// LinkedList: медленнее (cache-unfriendly)
for (int x : linkedList) {
// Узлы разбросаны в памяти - много cache misses
}
3. Память
// ArrayDeque: компактнее
ArrayDeque<String> aq = new ArrayDeque<>();
aq.add("A");
// Память: Object[] массив + int head/tail
// LinkedList: больше памяти
LinkedList<String> ll = new LinkedList<>();
ll.add("A");
// Память: Node (E item + Node next + Node prev) x N
// На каждый элемент 2 дополнительных ссылки (24 байта на 64-бит Java)
Примеры использования
ArrayDeque хорошо для:
// 1. Очередь (Queue)
Deque<Task> queue = new ArrayDeque<>();
queue.addLast(task1);
queue.addLast(task2);
Task t = queue.removeFirst(); // Быстро
// 2. Стек (Stack)
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1);
stack.push(2);
int top = stack.pop(); // Быстро
// 3. Алгоритмы со скользящим окном
ArrayDeque<Integer> window = new ArrayDeque<>();
// ... максимум в окне размера K
// 4. Поиск в ширину (BFS)
Deque<TreeNode> bfs = new ArrayDeque<>();
bfs.addLast(root);
while (!bfs.isEmpty()) {
TreeNode node = bfs.removeFirst();
// ... обработка
}
LinkedList хорошо для:
// 1. Частые удаления/вставки в середину (если у вас есть итератор)
LinkedList<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
ListIterator<String> it = list.listIterator(1);
it.next(); // B
it.add("X"); // Вставляет X между A и B - O(1)
// 2. Когда нужна реализация List (не только Deque)
List<String> list = new LinkedList<>();
list.get(0); // Медленно, но возможно
list.add(0, "First"); // Вставка в начало
// 3. Очень редко используется сейчас
Бенчмарк
Реальные числа (примерные):
Операция: добавить 1 млн элементов и итерировать
ArrayDeque:
- Память: ~4-8 MB
- Время итерации: 5 ms
- Скорость: 200 млн элементов/сек
LinkedList:
- Память: ~40-80 MB (в 10 раз больше!)
- Время итерации: 50 ms
- Скорость: 20 млн элементов/сек
Из чего это следует?
// ArrayDeque использует grow() для расширения
private void doubleCapacity() {
int n = elements.length;
int r = n - (head - tail); // Number of elements
int newCapacity = n << 1; // Удваиваем размер
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
Object[] a = new Object[newCapacity];
System.arraycopy(elements, head, a, 0, r);
System.arraycopy(elements, 0, a, r, head);
elements = a;
head = 0;
tail = r;
}
// LinkedList просто создаёт новый Node
private Node<E> linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(null, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
return newNode;
}
Правило большого пальца
Используй ArrayDeque по умолчанию для очередей и стеков!
// ✓ Правильно
Queue<Task> queue = new ArrayDeque<>(); // Быстро и эффективно
// ✗ Неправильно
Queue<Task> queue = new LinkedList<>(); // Медленнее и больше памяти
Исключения:
- Если нужны операции с индексом (get/set) - всё равно ArrayDeque
- Если нужна реализация List с частыми вставками в середину (редко)
- Если нужно хранить null'ы (LinkedList позволяет, ArrayDeque нет)
Итог
ArrayDeque почти всегда лучше LinkedList благодаря:
- Лучшей производительности (O(1) vs O(n) для индекса)
- Меньшему потреблению памяти
- Лучшей локальности кеша
LinkedList существует в основном для истории и edge cases. В современной Java ArrayDeque - стандартный выбор для очередей и стеков.