我正在阅读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
)。
答案 0 :(得分:258)
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)
,则在这种情况下为true
(因为newCapacity
为负),因此newCapacity
将被强制为minCapacity
oldCapacity
。
这个溢出案例由下一个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
评论所说的内容。
答案 1 :(得分:92)
我找到this explanation:
On Tue,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 }
这是一个很好的观点。
在
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
。
答案 2 :(得分:16)
查看代码:
int newCapacity = oldCapacity + (oldCapacity >> 1);
如果oldCapacity
非常大,则会溢出,newCapacity
将为负数。像newCapacity < oldCapacity
这样的比较会错误地评估true
,而ArrayList
将无法增长。
相反,编写的代码(newCapacity - minCapacity < 0
返回false)将允许在下一行中进一步评估newCapacity
的负值,从而通过调用{{{{}}来重新计算newCapacity
1}}(hugeCapacity
)允许newCapacity = hugeCapacity(minCapacity);
成长为ArrayList
。
这是MAX_ARRAY_SIZE
评论试图传达的内容,而不是倾斜。
因此,最重要的是,新比较可防止分配大于预定义// overflow-conscious code
的{{1}},同时允许其在需要时增长到该限制。
答案 3 :(得分:0)
除非表达式a - b
溢出,否则这两种形式的行为完全相同,在这种情况下它们是相反的。如果a
是一个较大的否定,并且b
是一个大的正数,那么(a < b)
显然是正确的,但a - b
会溢出变为正数,所以(a - b < 0)
是假的。
如果您熟悉x86汇编代码,请考虑(a < b)
由jge
实现,(a - b < 0)
在SF = OF时围绕if语句的主体分支。另一方面,jns
将表现为{{1}},当SF = 0时,它会分支。因此,当OF = 1时,这些行为的表现不同。