Разница между if (a-b

Я читал Java ArrayList исходный код и заметил некоторые сравнения в выражениях.

в Java 7, метод grow(int) использует

if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;

В Java 6, grow не существует. Метод ensureCapacity(int) использует

if (newCapacity < minCapacity)
    newCapacity = minCapacity;

какова была причина изменения? Это была проблема производительности или просто стиль?

я мог себе представить, что сравнение с нулем выполняется быстрее, но выполнение полного вычитание только для того, чтобы проверить, является ли оно отрицательным, кажется мне немного излишним. Также с точки зрения байт-кода это будет включать две инструкции (ISUB и IF_ICMPGE) вместо (IFGE).

4 ответов


a < b и a - b < 0 может означать две разные вещи. Рассмотрим следующий код:

int a = Integer.MAX_VALUE;
int b = Integer.MIN_VALUE;
if (a < b) {
    System.out.println("a < b");
}
if (a - b < 0) {
    System.out.println("a - b < 0");
}

при запуске это будет печатать только a - b < 0. Что происходит это a < b явно ложных, но a - b переполняется и становится -1, что отрицательно.

теперь, сказав это, подумайте, что массив имеет длину, которая действительно близка к Integer.MAX_VALUE. Код ArrayList выглядит так:

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);

oldCapacity очень близко к Integer.MAX_VALUE так newCapacity (т. е. oldCapacity + 0.5 * oldCapacity) может переполниться и стать Integer.MIN_VALUE (т. е. отрицательный). Затем, вычитая minCapacity underflows обратно в положительное число.

эта проверка гарантирует, что if не исполнено. Если бы код был написан как if (newCapacity < minCapacity) было бы true в этом случае (после newCapacity отрицательно) так что newCapacity был бы вынужден minCapacity независимо от oldCapacity.

этот случай переполнения обрабатывается следующим if. Когда newCapacity переполнился, это будет true: MAX_ARRAY_SIZE определяется как Integer.MAX_VALUE - 8 и Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0 is true. The newCapacity поэтому правильно обработано:hugeCapacity возвращает MAX_ARRAY_SIZE или Integer.MAX_VALUE.

NB: это то, что // overflow-conscious code комментарий в этом методе говорит.


нашел данное объяснение:

на Вт, 9 марта 2010 в 03: 02, Кевин л. Стерн написал:

я сделал быстрый поиск, и кажется, что Java действительно является дополнением двух основывающийся. Тем не менее, позвольте мне отметить, что в целом тип кода беспокоит меня, так как я полностью ожидаю, что в какой-то момент кто-то будет приходите и делайте именно то, что предложил Дмитрий; то есть кто-то будет изменить:

if (a - b > 0)

to

if (a > b)

и весь корабль утонет. Я, лично, как избежать неясности например, сделать переполнение integer существенным основанием для моего алгоритма, если для этого есть веская причина. Я бы, в общем, предпочел избежать переполнение вообще и сделать сценарий переполнения более явным:

if (oldCapacity > RESIZE_OVERFLOW_THRESHOLD) {
   // Do something
} else {
  // Do something else
}

это хороший момент.

на ArrayList мы не можем сделать это (или по крайней мере не совместимо), потому что ensureCapacity является публичным API и эффективно уже принимает отрицательные числа как запросы на положительный потенциал, который не может быть удовлетворенный.

текущий API используется следующим образом:

int newcount = count + len;
ensureCapacity(newcount);

если вы хотите избежать переполнения, вам нужно будет что-то изменить менее естественно, как

ensureCapacity(count, len);
int newcount = count + len;

в любом случае, я сохраняю код переполнения, но добавляю больше предупреждающие комментарии и "out-lining" огромное творение массива так, что ArrayListкод теперь выглядит так:

/**
 * Increases the capacity of this <tt>ArrayList</tt> instance, if
 * necessary, to ensure that it can hold at least the number of elements
 * specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
public void ensureCapacity(int minCapacity) {
    modCount++;

    // Overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/**
 * The maximum size of array to allocate.
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
private void grow(int minCapacity) {
    // Overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);

    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

Webrev регенерируется.

Мартин

в Java 6, Если вы используете API, как:

int newcount = count + len;
ensureCapacity(newcount);

и newCount переполнение (становится отрицательным), if (minCapacity > oldCapacity) возвращает false, и вы можете ошибочно предположить, что ArrayList была увеличена на len.


глядя на код:

int newCapacity = oldCapacity + (oldCapacity >> 1);

если oldCapacity довольно большой, это переполнение и newCapacity будет отрицательным числом. Сравнение как newCapacity < oldCapacity будет неправильно оценивать true и ArrayList не будет расти.

вместо этого, код как написано (newCapacity - minCapacity < 0 возвращает false) позволит отрицательное значение newCapacity для дальнейшей оценки в следующей строке, в результате перерасчета newCapacity ссылкой hugeCapacity (newCapacity = hugeCapacity(minCapacity);), чтобы ArrayList чтобы вырасти до MAX_ARRAY_SIZE.

это // overflow-conscious code комментарий пытается общаться, хотя и довольно косо.

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


две формы ведут себя точно так же, если выражение a - b переполнение, в этом случае они противоположны. Если a большой минус, и b является большим положительным, то (a < b) - Это верно, но a - b переполнение станет положительным, поэтому (a - b < 0) ложно.

если вы знакомы с кодом сборки x86, подумайте об этом (a < b) реализуется с помощью jge, который ветвится вокруг тела оператора if, когда SF = OF. С другой стороны, (a - b < 0) будет действовать как jns, который ветвится, когда SF = 0. Следовательно, они ведут себя по-разному именно тогда, когда OF = 1.