私はJavaのArrayList
ソースコードを読んでいて、if文でいくつかの比較に気づきました。
Java 7では、メソッド grow(int)
は
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
Java 6では、grow
は存在しませんでした。メソッド ensureCapacity(int)
ただし、
if (newCapacity < minCapacity)
newCapacity = minCapacity;
変更の背後にある理由は何ですか?それはパフォーマンスの問題なのでしょうか、それともスタイルなのでしょうか?
ゼロと比較する方が速いと想像できますが、マイナスかどうかを確認するためだけに完全な減算を実行することは、少しやり過ぎだと思います。また、バイトコードに関しては、1つ(ISUB
)ではなく2つの命令(IFGE
とIF_ICMPGE
)が関係します。
a < b
とa - b < 0
は、2つの異なることを意味します。次のコードを検討してください。
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
nderflowsを減算して正の数に戻します。
このチェックにより、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
を返します。
NB:これは、このメソッドの// overflow-conscious code
コメントが言っていることです。
私は見つけました この説明 :
2010年3月9日火曜日03:02に、Kevin L. Sternは次のように書いています。
クイック検索を行ったところ、Javaは実際には2の補数に基づいているようです。それにもかかわらず、一般的に、このタイプのコードは、ある時点で誰かがやって来て、Dmytroが提案したことを正確に実行することを完全に期待しているので、私を心配させます。つまり、誰かが変更されます:
if (a - b > 0)
に
if (a > b)
船全体が沈みます。個人的には、正当な理由がない限り、整数オーバーフローをアルゴリズムの重要な基盤にするなどのあいまいさを避けたいと思っています。一般に、オーバーフローを完全に回避し、オーバーフローシナリオをより明確にすることを好みます。
if (oldCapacity > RESIZE_OVERFLOW_THRESHOLD) { // Do something } else { // Do something else }
それは良い点です。
ArrayList
はパブリックAPIであり、満足できない正のキャパシティの要求として負の数をすでに事実上受け入れているため、ensureCapacity
ではこれを実行できません(少なくとも互換性がありません)。現在の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);
また、newCount
がオーバーフローすると(これは負になります)、if (minCapacity > oldCapacity)
はfalseを返し、ArrayList
がlen
だけ増加したと誤って想定する可能性があります。
コードを見る:
int newCapacity = oldCapacity + (oldCapacity >> 1);
oldCapacity
が非常に大きい場合、これはオーバーフローし、newCapacity
は負の数になります。 newCapacity < oldCapacity
のような比較はtrue
を誤って評価し、ArrayList
は大きくなりません。
代わりに、記述されたコード(newCapacity - minCapacity < 0
はfalseを返します)は、次の行でnewCapacity
の負の値をさらに評価し、newCapacity
を呼び出してhugeCapacity
を再計算します。 (newCapacity = hugeCapacity(minCapacity);
)ArrayList
がMAX_ARRAY_SIZE
に成長できるようにします。
これは、// overflow-conscious code
コメントが通信しようとしているものですが、かなり斜めになっています。
結論として、新しい比較は、定義済みのMAX_ARRAY_SIZE
よりも大きいArrayList
を割り当てることを防ぎ、必要に応じてその限界まで拡張できるようにします。
式a - b
がオーバーフローしない限り、2つの形式はまったく同じように動作します。 a
が大きな負で、b
が大きな正の場合、(a < b)
は明らかに真ですが、a - b
はオーバーフローして正になるため、(a - b < 0)
偽です。
X86アセンブリコードに精通している場合は、(a < b)
がjge
によって実装されていることを考慮してください。一方、(a - b < 0)
は、SF = 0の場合に分岐するjns
のように動作します。したがって、OF = 1の場合、これらは異なる動作をします。