← Назад к вопросам

Какая скорость чтения элемента в List?

1.0 Junior🔥 241 комментариев
#Коллекции

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Какая скорость чтения элемента в List? Временная сложность доступа

Это ещё один важный вопрос о структурах данных. Ответ зависит от типа List: ArrayList или LinkedList.

ArrayList (массив-based)

Скорость чтения: O(1) — прямой доступ

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

int element = list.get(1); // O(1) — мгновенно!
// Получаем элемент с индексом 1 без разницы, большой список или маленький

Почему O(1)?

ArrayList хранит элементы в массиве:

private Object[] elementData; // Внутреннее хранилище

public E get(int index) {
    return (E) elementData[index]; // Прямой доступ по индексу — O(1)
}

Массив в Java имеет прямой доступ — за O(1) мы можем получить элемент по индексу, независимо от позиции.

LinkedList (связный список)

Скорость чтения: O(n) — последовательный доступ

List<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
list.add(3);

int element = list.get(1); // O(n) — медленно!
// Нужно пройти от начала: 1 → 2 → получить

Почему O(n)?

LinkedList — это цепь узлов:

private Node<E> first;
private Node<E> last;

private static class Node<E> {
    E element;
    Node<E> next;
    Node<E> prev;
}

public E get(int index) {
    return getNode(index).element; // Нужно найти узел O(n)
}

private Node<E> getNode(int index) {
    if (index < size >> 1) { // Если индекс в первой половине
        Node<E> x = first;
        for (int i = 0; i < index; i++) {
            x = x.next; // Идём вперёд O(index)
        }
        return x;
    } else { // Если во второй половине
        Node<E> x = last;
        for (int i = size - 1; i > index; i--) {
            x = x.prev; // Идём назад O(size - index)
        }
        return x;
    }
}

Для доступа к элементу нужно пройти по ссылкам — это O(n) операция.

Сравнительная таблица

ОперацияArrayListLinkedListCopyOnWriteArrayList
get(index)O(1)O(n)O(1)
add(element)O(1) амортO(1)O(n)
add(index, element)O(n)O(n)O(n)
remove(index)O(n)O(n)O(n)
contains(object)O(n)O(n)O(n)

Практический анализ производительности

Чтение элементов

public class ListAccessPerformance {
    public static void main(String[] args) {
        int size = 1000000;
        
        // ArrayList
        List<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            arrayList.add(i);
        }
        
        long start = System.nanoTime();
        for (int i = 0; i < 100000; i++) {
            int random = (int) (Math.random() * size);
            int value = arrayList.get(random); // O(1)
        }
        long arrayListTime = System.nanoTime() - start;
        
        // LinkedList
        List<Integer> linkedList = new LinkedList<>(arrayList);
        
        start = System.nanoTime();
        for (int i = 0; i < 100000; i++) {
            int random = (int) (Math.random() * size);
            int value = linkedList.get(random); // O(n)
        }
        long linkedListTime = System.nanoTime() - start;
        
        System.out.println("ArrayList: " + arrayListTime / 1000000 + "ms");
        System.out.println("LinkedList: " + linkedListTime / 1000000 + "ms");
        System.out.println("Difference: " + (linkedListTime / arrayListTime) + "x");
    }
}

// Результат:
// ArrayList: ~10ms
// LinkedList: ~5000ms
// Difference: 500x!

Когда использовать какой List

1. ArrayList — если часто читаете элементы

// ХОРОШО: частый доступ
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");

for (int i = 0; i < names.size(); i++) {
    System.out.println(names.get(i)); // O(1) × size
}

2. LinkedList — если часто добавляете/удаляете в начало/конец

// ХОРОШО: операции в начале
Queue<Integer> queue = new LinkedList<>();
queue.add(1); // O(1) в конец
queue.add(2);
queue.add(3);

int first = queue.remove(); // O(1) из начала

3. Stream API — для последовательного доступа

List<Integer> list = new LinkedList<>();
// Плохо
for (int i = 0; i < list.size(); i++) {
    int value = list.get(i); // O(n) для каждого элемента!
}

// Хорошо
list.stream().forEach(value -> { }); // Использует iterator O(1) для каждого

// Или
for (Integer value : list) { // Использует iterator O(1)
    // ...
}

Оптимизации в современной Java

Оптимизация в LinkedList (с двусторонним доступом)

LinkedList<String> list = new LinkedList<>();
for (int i = 0; i < 1000; i++) {
    list.add("Element " + i);
}

// Плохо O(n)
String first = list.get(0); // Проходит от начала O(n)

// Хорошо O(1)
String first = list.getFirst(); // Прямой доступ к first указателю
String last = list.getLast(); // Прямой доступ к last указателю

Использование Iterator для LinkedList

// ПЛОХО: O(n²)
for (int i = 0; i < linkedList.size(); i++) {
    String element = linkedList.get(i); // O(n) операция!
}

// ХОРОШО: O(n)
for (String element : linkedList) { // Использует Iterator O(1) на элемент
    // ...
}

// Или явно
Iterator<String> iter = linkedList.iterator();
while (iter.hasNext()) {
    String element = iter.next(); // O(1)
}

Сложные сценарии

Случайный доступ

// ArrayList: O(1)
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5,6,7,8,9,10));
int random = list.get(5); // Быстро, O(1)

// LinkedList: O(n)
List<Integer> list = new LinkedList<>(Arrays.asList(1,2,3,4,5,6,7,8,9,10));
int random = list.get(5); // Медленно, O(n)

Последовательный доступ

// ArrayList: O(n)
for (Integer element : arrayList) {
    // O(1) на элемент × n элементов = O(n)
}

// LinkedList: O(n) 
for (Integer element : linkedList) {
    // O(1) на элемент × n элементов = O(n)
    // (если использовать Iterator, иначе O(n²))
}

Практические рекомендации

  1. По умолчанию используйте ArrayList

    • O(1) чтение
    • Хорошо масштабируется
    • Кэш-friendly
  2. LinkedList только если вам нужны

    • Операции добавления/удаления в начало O(1)
    • Queue, Deque интерфейсы
    • Итератор и удаление во время итерации
  3. Копируйте в ArrayList если часто читаете

    List<Integer> linkedList = new LinkedList<>();
    // ...
    
    // Если часто читаете, конвертируйте в ArrayList
    List<Integer> arrayList = new ArrayList<>(linkedList);
    
    for (int i = 0; i < size; i++) {
        int value = arrayList.get(i); // O(1) вместо O(n)
    }
    

Заключение

Ответ на вопрос:

  • ArrayList: O(1) — моментальный прямой доступ
  • LinkedList: O(n) — нужно пройти по цепи узлов

Для большинства сценариев ArrayList быстрее. LinkedList полезен только для специфических операций (добавление/удаление в начало, работа с Queue).