Использование размера коллекции для сравнения циклов - PullRequest
11 голосов
/ 14 декабря 2010

Есть ли оптимизация компилятора для методов size () коллекций в Java?

Рассмотрим следующий код:

for(int i=0;i<list.size();i++)
      ...some operation.....

Для каждого i существует вызов метода size (). Не лучше ли узнать размер и использовать его повторно? (У вызовов метода есть накладные расходы).

final int len = list.size()
for(int i=0;i<len;i++)
      ...some operation.....

Однако, когда я рассчитал время для обоих этих фрагментов кода, не было значительной разницы во времени, даже если я достиг 10000000. Я что-то здесь упускаю?

Обновление1: Я понимаю, что размер не вычисляется снова, пока не изменится коллекция. Но с вызовом метода должны быть связаны некоторые издержки. Это тот случай, когда компилятор всегда вставляет их (см. Ответ Эско)?

Обновление 2: Мое любопытство подпитывалось дальше. Из приведенных ответов я вижу, что хорошие JIT-компиляторы часто включают этот вызов функции. Но им все равно придется определить, была ли коллекция изменена или нет. Я не принимаю ответ в надежде, что кто-нибудь подскажет мне, как это обрабатывается компиляторами.

Ответы [ 4 ]

14 голосов
/ 15 декабря 2010

Хорошо, вот выдержка из источников JDK (src.zip в папке JDK):

public int size() {
    return size;
}

Это из ArrayList, но я думаю, что другие коллекции имеют аналогичные реализации.Теперь, если мы представим, что компилятор встроил вызов size () (что имело бы смысл), ваш цикл превращается в следующее:

for(int i=0;i<list.size;i++)
// ...

(Хорошо, давайте забудем, что размер является личным.) КакКомпилятор проверяет, была ли коллекция изменена?Ответ, что это не так и не нужно делать, потому что размер уже доступен в поле, поэтому все, что ему нужно сделать, это получить доступ к полю размера на каждой итерации, но получить доступ к переменной int очень быстро.операция.Обратите внимание, что он, вероятно, вычисляет свой адрес один раз, поэтому ему даже не нужно разыменовывать список на каждой итерации.

Что происходит, когда коллекция изменяется, скажем, методом add ()?

public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

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

9 голосов
/ 14 декабря 2010

Значение, возвращаемое методом .size() коллекции, обычно кэшируется и пересчитывается только при изменении фактической коллекции ( добавляются новые элементы или удаляются старые * ).

Вместо сравненияfor Область действия управления циклом, попробуйте использовать цикл for each, так как он на самом деле использует Iterator, что в некоторых реализациях коллекций намного быстрее, чем итерация с использованием индекса.

0 голосов
/ 14 декабря 2010

Спецификация языка Java объясняет, что выражение вычисляется на каждом шаге итерации .Например, list.size() вызывается 10.000.000 раз.

Это не имеет значения в вашем случае, потому что реализации списка (обычно) имеют закрытый атрибут, который хранит фактический размер списка.Но это может вызвать проблемы, если оценка действительно требует времени.В этих случаях желательно сохранить результат выражения в локальной переменной.

0 голосов
/ 14 декабря 2010

Вызов метода size () для коллекции просто возвращает целочисленное значение, которое уже отслежено. Разницы во времени не так много, потому что size () на самом деле не считает количество элементов, но вместо этого количество элементов отслеживается при их добавлении или удалении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...