Как динамически увеличивается емкость (capacity) ArrayList в Java?

Ответ

ArrayList увеличивает свою внутреннюю емкость (размер базового массива) по мере необходимости. Стандартный коэффициент роста — 1.5.

Механизм увеличения:

  1. При попытке добавить элемент, когда количество элементов (size) равно текущей емкости (capacity), срабатывает триггер на расширение.
  2. Создается новый массив большего размера.
  3. Новый размер вычисляется по формуле: newCapacity = oldCapacity + (oldCapacity >> 1).
    • oldCapacity >> 1 — это побитовый сдвиг вправо, эквивалентный целочисленному делению oldCapacity на 2.
    • Таким образом, newCapacity = oldCapacity * 1.5 (округление вниз).
  4. Все элементы копируются из старого массива в новый.

Пример:

ArrayList<Integer> list = new ArrayList<>(10); // Исходный массив length = 10
for (int i = 0; i < 10; i++) list.add(i); // size=10, capacity=10
list.add(10); // Триггер! Вычисление: 10 + (10 >> 1) = 10 + 5 = 15
// Теперь capacity = 15, size = 11. Базовый массив length = 15.

Важные детали:

  • Начальная емкость: По умолчанию равна 10. Ее можно задать через конструктор ArrayList(int initialCapacity) для оптимизации, если известен ожидаемый размер.
  • Ручное управление: Метод ensureCapacity(int minCapacity) позволяет заранее увеличить внутренний массив до указанного размера, избегая многократных копирований при последовательном добавлении.
    ArrayList<String> list = new ArrayList<>();
    list.ensureCapacity(1000); // Сразу выделяется массив под 1000 элементов
    // Последующие 1000 вызовов add() не вызовут переаллокации.
  • Уменьшение емкости: Метод trimToSize() уменьшает емкость до текущего размера списка, освобождая лишнюю память.

Ответ 18+ 🔞

А, ну этот ваш ArrayList... Ну, классика же, блядь! Сидит себе тихонечко, как мартышлюшка на дереве, а потом — бац! — и ему уже тесно, сука. А он не дурак, ёпта, он ж не будет вечно в одних и тех же штанах ходить, когда жопа выросла!

Так вот, слушай сюда, как он работает, этот хитрожопый механизм. У него там внутри обычный массив, да. И когда ты начинаешь туда пихать элементы, как в переполненный автобус в час пик, наступает момент, блядь, когда места уже нихуя нет. size сравнивается с capacity — и тут начинается магия, ёбана!

Он не просто на один элемент увеличивается — это было бы пиздец как неэффективно, каждый раз переезжать в новую квартиру из-за одной новой тапки. Нет! Он умножает текущую ёмкость на полтора, блядь! Формула такая: новая_вместимость = старая_вместимость + (старая_вместимость >> 1). Этот >> 1 — это типа поделить на два, но по-компьютерному, быстро, без лишних разговоров.

Пример, чтобы совсем понятно стало:

ArrayList<Integer> list = new ArrayList<>(10); // Создал квартиру на 10 комнат
for (int i = 0; i < 10; i++) list.add(i); // Заселил 10 жильцов. Всё, пиздец, мест нет!
list.add(10); // А ты ещё одного привёл! ТРИГГЕР, СУКА!
// Расчёт: 10 + (10 >> 1) = 10 + 5 = 15.
// Всё, переезжаем в новую хату на 15 комнат. Capacity теперь 15.

А теперь лайфхаки, блядь, чтобы не ебаться с этим:

  • Стартовая позиция: По дефолту он скромный, на 10 элементов. Но если ты заранее знаешь, что у тебя будет овердохуища данных, скажи ему! new ArrayList<>(1000). И всё, сразу большой массив выделит, не будет потом каждые 5 шагов переезжать.
  • Ручное вмешательство: Есть метод ensureCapacity(). Это как крикнуть: "Эй, пацан, готовься, щас мы тебя наебём количеством!". Вызвал его с числом 5000 — и он сразу выделит память под 5000 элементов. Красота, ни одного лишнего копирования!
  • Уборка после вечеринки: Напихал кучу всего, потом половину удалил — а память-то пустая болтается. Метод trimToSize() — это как сказать: "Да похуй, сожмись до своих реальных размеров, не позорься". Он освободит лишнее место.

Вот и вся философия. Умный мудак, что тут скажешь. Сам от себя охуеешь, когда поймёшь, насколько это гениально просто.