if(a - b <0)和if(a <b)之间的差异

dej*_*uth 233 java if-statement arraylist

我正在阅读Java的ArrayList源代码,并注意到if语句中的一些比较.

在Java 7中,该方法grow(int)使用

if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
Run Code Online (Sandbox Code Playgroud)

在Java 6中,grow不存在.ensureCapacity(int)然而,该方法使用

if (newCapacity < minCapacity)
    newCapacity = minCapacity;
Run Code Online (Sandbox Code Playgroud)

改变背后的原因是什么?这是性能问题还是风格?

我可以想象,与零进行比较会更快,但执行完全减法只是为了检查它是否为负似乎对我来说有点矫枉过正.同样在字节码方面,这将涉及两个指令(ISUBIF_ICMPGE)而不是一个(IFGE).

Tun*_*aki 259

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");
}
Run Code Online (Sandbox Code Playgroud)

运行时,只会打印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);
Run Code Online (Sandbox Code Playgroud)

oldCapacity是真正贴近Integer.MAX_VALUE所以newCapacity(这是oldCapacity + 0.5 * oldCapacity)可能会溢出,并成为Integer.MIN_VALUE(即负).然后,将minCapacity 下溢减去正数.

此检查确保if不执行.如果代码被写为if (newCapacity < minCapacity),则true在这种情况下(因为newCapacity是否定的)所以newCapacity无论如何都将被强制minCapacity执行oldCapacity.

这个溢出的情况由下一个if处理.当newCapacity已经溢出,这将是true:MAX_ARRAY_SIZE被定义为Integer.MAX_VALUE - 8Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0true.将newCapacity因此被正确地处理:hugeCapacity方法返回MAX_ARRAY_SIZEInteger.MAX_VALUE.

注意:这就是// overflow-conscious code这种方法中的评论所说的.

  • @piggybox我不会这么说.这是数学.它只是不是Z中的数学,而是在模数为2 ^ 32的整数版本中(规范表示的选择与通常不同).这是一个合适的数学系统,而不仅仅是"lol计算机及其怪癖". (30认同)
  • 关于数学和CS之间差异的好演示 (7认同)
  • 我会编写完全不会溢出的代码。 (2认同)
  • @ BenC.R.Leggiero x86等通过单独寄存器中的状态标志跟踪各种条件,以便与条件指令一起使用.该寄存器具有用于结果符号的单独位,结果的zeroness,以及在最后的算术运算中是否发生上溢/下溢. (2认同)

Era*_*ran 92

我找到了这个解释:

2010年3月9日星期二03:02,Kevin L. Stern写道:

我做了一个快速搜索,看起来Java确实是两个补充.尽管如此,请允许我指出,一般来说,这种类型的代码让我很担心,因为我完全相信某些人会出现并完全按照Dmytro的建议行事; 也就是说,有人会改变:

if (a - b > 0)
Run Code Online (Sandbox Code Playgroud)

if (a > b)
Run Code Online (Sandbox Code Playgroud)

而整艘船都会沉没.我个人喜欢避免使用整数溢出这样的晦涩难懂,这是我的算法必不可少的基础,除非有充分的理由这样做.一般来说,我宁愿完全避免溢出并使溢出场景更明确:

if (oldCapacity > RESIZE_OVERFLOW_THRESHOLD) {
   // Do something
} else {
  // Do something else
}
Run Code Online (Sandbox Code Playgroud)

这是一个好点.

ArrayList我们不能做到这一点(或至少不兼容),因为 ensureCapacity是一个公共的API,有效地接受已经为负数的不能满足一个积极的容量请求.

当前的API使用如下:

int newcount = count + len;
ensureCapacity(newcount);
Run Code Online (Sandbox Code Playgroud)

如果你想避免溢出,你需要改变一些不太自然的东西

ensureCapacity(count, len);
int newcount = count + len;
Run Code Online (Sandbox Code Playgroud)

无论如何,我保持溢出意识的代码,但添加更多警告注释,并"超出"巨大的数组创建,所以 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;
}
Run Code Online (Sandbox Code Playgroud)

Webrev重生了.

马丁

在Java 6中,如果您将API用作:

int newcount = count + len;
ensureCapacity(newcount);
Run Code Online (Sandbox Code Playgroud)

并且newCount溢出(这变为负数),if (minCapacity > oldCapacity)将返回false并且您可能错误地认为ArrayList增加了len.

  • 不错的想法,但它与[ensureCapacity`的实现相矛盾](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/ArrayList. Java的#178); 如果`minCapacity`为负数,那么你永远不会达到这一点 - 它就像复杂的实现假装要防止一样被忽略.因此,对于公共API兼容性而言,"我们不能这样做"是一个奇怪的论点,因为他们已经做到了.依赖此行为的唯一调用者是内部调用者. (2认同)

Eri*_*rom 16

看代码:

int newCapacity = oldCapacity + (oldCapacity >> 1);
Run Code Online (Sandbox Code Playgroud)

如果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同时允许它在需要时增长到该限制.