Как определить понятие емкости в ArrayLists?

Я понимаю, что емкость — это количество элементов или доступных пространств в ArrayList, которые могут содержать или не содержать значение, ссылающееся на объект. Я пытаюсь понять больше о концепции емкости.

Итак, у меня три вопроса:

1) Какие есть хорошие способы определить, что представляет емкость с точки зрения памяти?

... (непрерывная?) память, выделенная для ArrayList?

... объем памяти ArrayLists в (куче?)?

2) Тогда, если вышеизложенное верно, изменение емкости требует каких-то накладных расходов на управление памятью?

3) У кого-нибудь есть пример, где № 2 был или мог быть проблемой производительности? Помимо, возможно, большого количества больших списков массивов, емкость которых постоянно корректируется?


person Cephi    schedule 23.03.2011    source источник
comment
Если бы вы знали C, вы бы делали такие вещи, как выделение памяти, но да, они перераспределяют память по мере ее динамического роста (с некоторым коэффициентом, я думаю, 1,75 или что-то в этом роде). Всегда лучше создавать экземпляр, зная размер, иначе, как вы сказали в 3, когда вы добавляете много данных он будет выделять много пустого пространства. Я дам людям, которые знают намного больше о распределении памяти и о том, как это делается в Java, дать реальные ответы.   -  person Mike    schedule 23.03.2011
comment
(с некоторым коэффициентом, я думаю, 1,75 или что-то в этом роде) формула int newCapacity = (oldCapacity * 3)/2 + 1;   -  person bestsss    schedule 23.03.2011


Ответы (4)


  1. Класс называется ArrayList, потому что он основан на массиве. Емкость — это размер массива, для которого требуется блок непрерывной памяти кучи. Однако обратите внимание, что сам массив содержит только ссылки на элементы, которые являются отдельными объектами в куче.
  2. Увеличение емкости требует выделения нового массива большего размера и копирования всех ссылок из старого массива в новый, после чего старый становится пригодным для сборки мусора.
  3. Вы упомянули основной случай, когда производительность может быть проблемой. На практике я никогда не видел, чтобы это действительно становилось проблемой, поскольку объекты-элементы обычно занимают гораздо больше памяти (и, возможно, процессорного времени), чем список.
person Michael Borgwardt    schedule 23.03.2011

ArrayList реализован так:

class ArrayList {
  private Object[] elements;
}

емкость - это размер этого массива.

Теперь, если ваша емкость равна 10, и вы добавляете 11-й элемент, ArrayList сделает следующее:

Object[] newElements = new Object[capacity * 1.5];
System.arraycopy(this.elements, newElements);
this.elements = newElements;

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

С другой стороны, если вы укажете емкость 1 000 000 и добавите в ArrayList только 3 элемента, это тоже будет плохо.

Правило большого пальца: если вы знаете емкость, укажите ее. Если вы не уверены, но знаете верхнюю границу, укажите это. Если вы просто не уверены, используйте значения по умолчанию.

person iluxa    schedule 23.03.2011

Емкость - это то, что вы описали, - непрерывная память, выделенная ArrayList для хранения значений. ArrayList сохраняет все значения в массиве и автоматически изменяет размер массива для вас. Это влечет за собой накладные расходы на управление памятью при изменении размера.

Если я правильно помню, Java увеличивает размер резервного массива ArrayList с размера N до размера 2N + 2, когда вы пытаетесь добавить еще один элемент, чем может вместить емкость. Я не знаю, до какого размера он увеличивается, когда вы используете метод insert (или аналогичный) для вставки в определенную позицию за пределами емкости, и даже позволяет ли он это.

Вот пример, который поможет вам подумать о том, как это работает. Представьте каждое пространство между | как ячейку в резервном массиве:

| | |

размер = 0 (не содержит элементов), емкость = 2 (может содержать 2 элемента).

|1| |

размер = 1 (содержит 1 элемент), емкость = 2 (может содержать 2 элемента).

|1|2|

размер = 2, емкость = 2. Добавление еще одного элемента:

|1|2|3| | | |

размер увеличен на 1, вместимость увеличена до 6 (2 * 2 + 2). Это может быть дорого с большими массивами, так как выделение большой непрерывной области памяти может потребовать немного работы (в отличие от LinkedList, который выделяет много маленьких частей памяти), потому что JVM необходимо искать подходящее место и, возможно, потребуется запросить у ОС дополнительную память. Также дорого стоит копировать большое количество значений из одного места в другое, что будет сделано после того, как такая область будет найдена.

Мое эмпирическое правило таково: если вы знаете, какая емкость вам потребуется, используйте ArrayList, потому что будет только одно выделение, а доступ будет очень быстрым. Если вы не знаете требуемую емкость, используйте LinkedList, потому что добавление нового значения всегда требует одинакового объема работы и не требует копирования.

person Jonathan    schedule 23.03.2011
comment
Поскольку каждый новый элемент связанного списка создает новый узел списка, добавление элементов в такой список имеет постоянные накладные расходы, которые в среднем, вероятно, больше, чем добавление элемента в конец списка массивов, даже включая повторно растущие массивы (поскольку они встречаются очень редко). Я бы использовал связанный список, только если я хочу добавить элементы впереди (или в позиции, найденной итератором), или если вам действительно нужен гарантированный O (1) для ваших дополнений, а не только амортизированный O (1). - person Paŭlo Ebermann; 24.03.2011
comment
@Paŭlo Правда, но я все равно предпочитаю их. И если вам нужна очередь, это действительно единственный путь. - person Jonathan; 24.03.2011
comment
@Paŭlo Ebermann ... Знаете, я только что прочитал обратное в Java SE6 Мураха. Мурах заявляет, что, поскольку LinkedLists не используют массивы, вставка элемента в LinkedList может быть более эффективной, поскольку необходимо изменить только предыдущий и следующий указатели, а не создавать целый новый массив, как это делает ArrayList. Возможно, вы думали о доступе элемент, который требует много накладных расходов, поскольку указатели каждого элемента должны быть пошаговыми, чтобы добраться до места назначения. - person Cephi; 01.04.2011
comment
@Cephi: Конечно, если у вас есть ArrayList на полную мощность (т. Е. Во внутреннем массиве больше нет свободного места), добавление еще одного элемента занимает больше времени, чем в связанном списке. Но если вы добавите в такой список n элементов (изначально пустых), такое изменение размера и копирование произойдет только O(log(n)) раза (и копирование менее 2·n элементов массива), при этом вам все равно потребуется создать n новых объектов узла списка и установить 3·n указатели. (Конечно, это для добавления элементов в конце.) - person Paŭlo Ebermann; 01.04.2011
comment
@Cephi: при добавлении в список в начале связанный список имеет явное преимущество, поскольку в ArrayList для каждого элемента мы должны сдвинуть все следующие элементы на один назад, а в случае связанного списка мы просто необходимо создать один объект узла и установить/изменить 4 указатели. - person Paŭlo Ebermann; 01.04.2011
comment
@Cephi: для добавления в середине это уравновешивается: в связанном списке мы сначала должны искать правильный узел, повторяя итератор списка, который занимает до n/2 обходов ссылок. Само добавление стоит столько же, сколько и каждый раз. В массиве не нужно много времени, чтобы найти правильную позицию, но мы должны скопировать все следующие элементы, чтобы сдвинуть их. - person Paŭlo Ebermann; 01.04.2011
comment
Для достаточно быстрого добавления/удаления везде (и на основе индекса) нам понадобится древовидный список, такой как TreeList в Apache commons (который требует O(log n) для каждого типа доступа к одному элементу (добавление, извлечение, удаление). - person Paŭlo Ebermann; 01.04.2011
comment
@Paŭlo Ebermann Вау, ты быстрый. Я только что просматривал другую книгу, и в ней говорилось то, к чему, я думаю, вы стремились: существуют накладные расходы памяти, связанные с указателями каждого элемента в LinkedList. Это может быть больше, чем память, используемая ArrayList, если только он не имеет слишком большой набор емкости. Я отредактирую свой комментарий выше, чтобы не было похоже, что вы были неправы. - person Cephi; 01.04.2011
comment
Накладные расходы памяти, а также накладные расходы времени обработки. Но как всегда, измерьте его, если считаете, что это узкое место вашего приложения, и только потом думайте о его изменении :-) - person Paŭlo Ebermann; 01.04.2011

1) Какие есть хорошие способы определить, что представляет емкость с точки зрения памяти?

... (непрерывная?) память, выделенная для ArrayList?

Да, ArrayList поддерживается массивом, который представляет внутренний размер массива.

... объем памяти, занимаемой ArrayLists в (куче?)?

Да, чем больше емкость массива, тем больше места занимает массив.

2) Тогда, если вышесказанное верно, изменение емкости требует некоторых накладных расходов на управление памятью?

Это. Когда список становится достаточно большим, выделяется массив большего размера и копируется его содержимое. Предыдущий массив может быть отброшен и помечен для сборки мусора.

3) У кого-нибудь есть пример, где № 2 был или мог быть проблемой производительности? Помимо, возможно, большого количества больших списков массивов, емкость которых постоянно корректируется?

Да, если вы создаете ArrayList с начальной емкостью 1 (например), и ваш список выходит за эти пределы. Если вы заранее знаете количество элементов для хранения, вам лучше запросить начальную емкость этого размера.

Однако я думаю, что это должно быть последним в вашем списке приоритетов, в то время как копирование массива может происходить очень часто, оно оптимизировано с ранних стадий Java и не должно вызывать беспокойства. Думаю, лучше было бы выбрать правильный алгоритм. Помните: Преждевременная оптимизация — корень всех зол

См. также: Когда использовать LinkedList вместо ArrayList

person OscarRyz    schedule 23.03.2011