Java中的overflow-conscious code

1. 背景

在jdk源码中,会有很多考虑了溢出而编写的代码,这些代码前会有注释:"overflow-conscious code",说明下面这段代码是考虑了溢出的情况的。最经典的代码就是 ArrayList 里的 grow() 方法,如下所示:

/**
 * 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);
}

2. 分析

这里说考虑了溢出的情况,是如何考虑了溢出呢?考虑了哪个变量的溢出呢?在溢出的情况下是怎么应对的呢?

3. 解释

在计算机中,a - b < 0a < b 并不完全等价。在没有发生溢出的情况下,两者是等价的,但是如果发生了溢出,情况将会变得不同。考虑下面的代码:

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 由于发生了溢出,因此结果是负的。

说清楚了这些背景知识后,下面详细分析一下 ArrayList#grow() 的代码:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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);
}

假设 oldCapacity 接近 Integer.MAX_VALUE 的时候,那么 newCapacity = newCapacity = oldCapacity + (oldCapacity >> 1) ,此时 newCapacity 必然发生溢出,从而变成负数。

newCapacity 发生溢出时 ,如果使用 newCapacity < minCapacity 和 来进行判断,判断结果很明显为 true,此时就会走 if 表达式中的内容,将 newCapacity 设置为 minCapacity。这种情况从逻辑上来说很明显不正确。

因此这里使用的是 if (newCapacity - minCapacity < 0)if (newCapacity - MAX_ARRAY_SIZE > 0) 来判断。当 newCapacity 为负数时,newCapacity - minCapacity 必然也会发生溢出,也就意味着 newCapacity - minCapacity 的结果是大于0的,这种情况下就会跳过执行 if (newCapacity - MAX_ARRAY_SIZE > 0) 中的逻辑。此时第二个if条件 newCapacity - MAX_ARRAY_SIZE 的结果肯定也是大于0的,此时 newCapacity = hugeCapacity(minCapacity),从而消除了 newCapacity 发生溢出的情况。

这里解释下为什么 newCapacity - minCapacitynewCapacity - MAX_ARRAY_SIZE 的结果都是大于0的

通过源码追上去,可以发现 minCapacity = oldCapacity + 1,如果 newCapacity 发生了溢出,因为 oldCapacity 的最大值为 Integer.MAX_VALUE,所以 newCapacity 的最大值为 - Integer.MAX_VALUE >> 1,所以 newCapacity - minCapacity 会发生溢出,从而导致结果为正数,同理 newCapacity - MAX_ARRAY_SIZE 也会发生溢出导致结果为正数。

4. 总结

从上面的解释中可以看到,Java源码中这么写同时兼顾到了溢出和非溢出的情况,且无论溢出不溢出,可以始终保持扩容后的 newCapacity 结果为正数。这也就是代码中的注释 // overflow-conscious code 的意义。

参考:Difference between if (a - b < 0) and if (a < b)

上一篇:BAT面试必问:a-b0与ab什么区别?overflow-conscious代码什么玩意?


下一篇:Stack的底层Vector源码浅析