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

2022-08-31 05:32:58

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

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

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

在Java 6中,不存在。该方法确保容量(int)但是使用grow

if (newCapacity < minCapacity)
    newCapacity = minCapacity;

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

我可以想象与零进行比较更快,但是执行完整的减法只是为了检查它是否为负数对我来说似乎有点过分了。同样就字节码而言,这将涉及两条指令(和)而不是一条()。ISUBIF_ICMPGEIFGE


答案 1

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 < 0a < ba - b-1

现在,话虽如此,请考虑数组的长度非常接近 。中的代码如下所示:Integer.MAX_VALUEArrayList

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_VALUEnewCapacityoldCapacity + 0.5 * oldCapacityInteger.MIN_VALUEminCapacity

此检查可确保 不执行 。如果代码写为 ,则在这种情况下(因为 是负数),因此将强制不考虑 .ifif (newCapacity < minCapacity)truenewCapacitynewCapacityminCapacityoldCapacity

此溢出情况由下一个 if 处理。当 溢出时,这将是 : 定义为 和 是 。因此,正确处理了:方法返回或 。newCapacitytrueMAX_ARRAY_SIZEInteger.MAX_VALUE - 8Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0truenewCapacityhugeCapacityMAX_ARRAY_SIZEInteger.MAX_VALUE

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


答案 2

我发现这个解释

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

我做了一个快速搜索,似乎Java确实是基于两个补码的。尽管如此,请允许我指出,总的来说,这种类型的代码让我感到担忧,因为我完全期望在某个时候有人会来做Dmytro建议的事情;也就是说,有人会改变:

if (a - b > 0)

if (a > b)

整艘船将沉没。就我个人而言,我喜欢避免一些晦涩难懂的事情,比如让整数溢出成为我的算法的基本基础,除非有充分的理由这样做。一般来说,我宁愿完全避免溢出,并使溢出方案更加明确:

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

这是一个很好的观点。

我们不能这样做(或者至少不兼容),因为这是一个公共API,并且实际上已经接受负数作为无法满足的正容量的请求。ArrayListensureCapacity

当前的 API 是这样的:

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

如果你想避免溢出,你需要换成不那么自然的东西,比如

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

无论如何,我保留了溢出意识的代码,但添加了更多的警告注释,并“外线”了巨大的数组创建,因此现在的代码看起来像这样: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);

并且溢出(这变为负数)将返回 false,并且您可能会错误地认为 增加了 。newCountif (minCapacity > oldCapacity)ArrayListlen


推荐