if(a – b <0)和if(a <b)
我正在阅读Java的ArrayList
源代码,并注意到if语句中的一些比较。
在Java 7中, grow(int)
方法使用
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
在Java 6中, grow
并不存在。 然后使用ensureCapacity(int)
方法
if (newCapacity < minCapacity) newCapacity = minCapacity;
这个变化背后的原因是什么? 这是一个性能问题还是一个风格?
我可以想象,比较零比较快,但执行一个完整的减法只是为了检查是否为负面似乎有点矫枉过正给我。 同样在字节码方面,这将涉及两个指令( ISUB
和IF_ICMPGE
)而不是一个( IFGE
)。
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
下溢减去一个正数。
这个检查确保if
不被执行。 如果代码写成if (newCapacity < minCapacity)
,则在这种情况下(因为newCapacity
为负),所以newCapacity
将被强制为minCapacity
而不pipeoldCapacity
。
这个溢出的情况是由下一个if处理的。 当newCapacity
溢出时,这将true
: MAX_ARRAY_SIZE
被定义为Integer.MAX_VALUE - 8
, Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0
为true
。 因此, newCapacity
被正确处理: hugeCapacity
方法返回MAX_ARRAY_SIZE
或Integer.MAX_VALUE
。
注意:这是这个方法中// overflow-conscious code
注释的意思。
我find了这个解释 :
在星期二,2010年3月9日在03:02,凯文L.斯特恩写道:
我做了一个快速的search,似乎Java确实是基于二进制的。 尽pipe如此,请允许我指出,总的来说,这种types的代码令我感到担忧,因为我完全期望在某个时候有人会出现并且完全按照Dmytro的build议去做。 也就是说有人会改变:
if (a - b > 0)
至
if (a > b)
整艘船将沉没。 我个人喜欢避免使用整数溢出这样的晦涩之处,这是我的algorithm必不可less的基础,除非有足够的理由这么做。 总的来说,我宁愿避免完全溢出,并使溢出情况更加明确:
if (oldCapacity > RESIZE_OVERFLOW_THRESHOLD) { // Do something } else { // Do something else }
这是一个很好的观点。
在
ArrayList
我们不能这样做(或者至less不兼容),因为ensureCapacity
是一个公共的API,并且已经接受了负数,因为请求的容量不能满足。当前的API是这样使用的:
int newcount = count + len; ensureCapacity(newcount);
如果你想避免溢出,你需要改变一些不太自然的东西
ensureCapacity(count, len); int newcount = count + len;
无论如何,我保持溢出意识的代码,但添加更多的警告评论,并“外插”巨大的数组创build,使
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);
if (minCapacity > oldCapacity)
将返回false,并且您可能错误地认为ArrayList
增加了len
,并且newCount
溢出(这变成负数)。
看代码:
int newCapacity = oldCapacity + (oldCapacity >> 1);
如果oldCapacity
相当大,则会溢出, newCapacity
将为负数。 比如newCapacity < oldCapacity
将错误地评估true
, ArrayList
将无法增长。
相反,写入的代码( newCapacity - minCapacity < 0
返回false)将允许newCapacity
的负值在下一行中进一步评估,从而通过调用hugeCapacity
( newCapacity = hugeCapacity(minCapacity);
)重新计算newCapacity
以允许ArrayList
长到MAX_ARRAY_SIZE
。
这是// overflow-conscious code
注释尝试传达的内容,尽pipe相当倾斜。
所以,底线,新的比较防止分配比预定义的MAX_ARRAY_SIZE
大的ArrayList
同时允许它在需要时增长到该限制。
除非expression式a - b
溢出,否则两种expression式完全相同,在这种情况下它们是相反的。 如果a
是一个大的负数, b
是一个大的正数,那么(a < b)
显然是正确的,但a - b
会溢出变成正数,所以(a - b < 0)
是错误的。
如果您熟悉x86汇编代码,请考虑(a < b)
由jge
实现,它在SF = OF时围绕if语句的主体进行分支。 另一方面, (a - b < 0)
将performance为jns
,当SF = 0时分支。因此,当OF = 1时,它们的行为不同。