Ich las Javas ArrayList
-Quellcode und bemerkte einige Vergleiche in if-Anweisungen.
In Java 7 wird die Methode grow(int)
verwendet
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
In Java 6 war grow
nicht vorhanden. Die Methode ensureCapacity(int)
verwendet jedoch
if (newCapacity < minCapacity)
newCapacity = minCapacity;
Was war der Grund für die Veränderung? War es ein Leistungsproblem oder nur ein Stil?
Ich könnte mir vorstellen, dass der Vergleich mit Null schneller ist, aber eine vollständige Subtraktion, nur um zu prüfen, ob sie negativ ist, erscheint mir etwas übertrieben. Auch in Bezug auf Bytecode würde dies zwei Anweisungen (ISUB
und IF_ICMPGE
) anstelle von einer (IFGE
) beinhalten.
a < b
und a - b < 0
können zwei verschiedene Bedeutungen haben. Betrachten Sie den folgenden Code:
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");
}
Bei Ausführung wird nur a - b < 0
gedruckt. Was passiert ist, dass a < b
eindeutig falsch ist, aber a - b
überläuft und -1
wird, was negativ ist.
Bedenken Sie jedoch, dass das Array eine Länge hat, die wirklich nahe an Integer.MAX_VALUE
liegt. Der Code in ArrayList
sieht folgendermaßen aus:
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
ist wirklich in der Nähe von Integer.MAX_VALUE
, daher könnte newCapacity
(was oldCapacity + 0.5 * oldCapacity
ist) überlaufen und Integer.MIN_VALUE
(d. h. negativ) werden. Subtrahieren Sie dann minCapacity
underflows zurück in eine positive Zahl.
Diese Prüfung stellt sicher, dass die if
nicht ausgeführt wird. Wenn der Code als if (newCapacity < minCapacity)
geschrieben würde, wäre dies in diesem Fall true
(da newCapacity
negativ ist), sodass die newCapacity
unabhängig von minCapacity
zur oldCapacity
gezwungen wird.
Dieser Überlauffall wird vom nächsten if behandelt. Wenn newCapacity
übergelaufen ist, ist dies true
: MAX_ARRAY_SIZE
ist definiert als Integer.MAX_VALUE - 8
und Integer.MIN_VALUE - (Integer.MAX_VALUE - 8) > 0
ist true
. Die Variable newCapacity
wird daher richtig behandelt: Die Methode hugeCapacity
gibt MAX_ARRAY_SIZE
oder Integer.MAX_VALUE
zurück.
NB: Das sagt der // overflow-conscious code
-Kommentar in dieser Methode.
Ich habe diese Erklärung gefunden :
Am Dienstag, 9. März 2010 um 03:02 Uhr schrieb Kevin L. Stern:
Ich habe eine schnelle Suche durchgeführt und es scheint, dass Java in der Tat das Zweierpack ist basierend. Erlauben Sie mir trotzdem, darauf hinzuweisen, dass diese Die Art des Codes macht mir Sorgen, da ich völlig damit rechne, dass irgendwann jemand Kommen Sie mit und machen Sie genau das, was Dmytro vorgeschlagen hat. das heißt, jemand wird Veränderung:
if (a - b > 0)
zu
if (a > b)
und das ganze Schiff wird sinken. Ich persönlich mag es, Unklarheiten zu vermeiden B. den Integer-Überlauf zu einer wesentlichen Grundlage für meinen Algorithmus machen, sofern nicht Dafür gibt es einen guten Grund. Im Allgemeinen würde ich es vorziehen, zu vermeiden Überlauf insgesamt und um das Überlaufszenario deutlicher zu machen:
if (oldCapacity > RESIZE_OVERFLOW_THRESHOLD) { // Do something } else { // Do something else }
Das ist ein guter Punkt.
In
ArrayList
können wir das nicht (oder zumindest nicht kompatibel), weilensureCapacity
ist eine öffentliche API und akzeptiert bereits negative Zahlen als Anfragen nach einer positiven Kapazität, die nicht sein kann zufrieden.Die aktuelle API wird wie folgt verwendet:
int newcount = count + len; ensureCapacity(newcount);
Wenn Sie einen Überlauf vermeiden möchten, müssen Sie etwas ändern weniger natürlich
ensureCapacity(count, len); int newcount = count + len;
Wie auch immer, ich halte den überlaufbewussten Code, füge aber mehr .__ hinzu. Warnungskommentare und "Outlining" der Erstellung großer Arrays, so dass Der Code von
ArrayList
sieht jetzt so aus:/** * 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 neu erstellt.
Martin
Wenn Sie in Java 6 die API wie folgt verwenden:
int newcount = count + len;
ensureCapacity(newcount);
Und newCount
läuft über (dies wird negativ), if (minCapacity > oldCapacity)
wird false zurückgeben und Sie können irrtümlicherweise annehmen, dass ArrayList
um len
erhöht wurde.
Blick auf den Code:
int newCapacity = oldCapacity + (oldCapacity >> 1);
Wenn oldCapacity
ziemlich groß ist, wird dies überlaufen und newCapacity
ist eine negative Zahl. Ein Vergleich wie newCapacity < oldCapacity
wertet true
falsch aus und die ArrayList
wächst nicht.
Stattdessen ermöglicht der Code, wie geschrieben (newCapacity - minCapacity < 0
gibt false), den negativen Wert von newCapacity
in der nächsten Zeile weiter auszuwerten, was zur Neuberechnung von newCapacity
durch Aufrufen von hugeCapacity
(newCapacity = hugeCapacity(minCapacity);
) führt, damit die ArrayList
auf MAX_ARRAY_SIZE
aufwachsen kann.
Dies ist es, was der // overflow-conscious code
-Kommentar versucht, zu kommunizieren, wenn auch etwas schräg.
Unterm Strich schützt der neue Vergleich also vor der Zuweisung einer ArrayList
, die größer als der vordefinierte MAX_ARRAY_SIZE
ist, und lässt ihn bei Bedarf bis zu diesem Grenzwert anwachsen.
Die beiden Formulare verhalten sich genau gleich, wenn der Ausdruck a - b
nicht überläuft. In diesem Fall liegen sie entgegengesetzt. Wenn a
ein großes Negativ ist und b
ein großes Positiv ist, ist (a < b)
eindeutig wahr, aber a - b
wird überlaufen und wird positiv, so dass (a - b < 0)
falsch ist.
Wenn Sie mit x86-Assemblycode vertraut sind, sollten Sie bedenken, dass (a < b)
von einer jge
implementiert wird, die sich bei SF = OF um den Rumpf der if-Anweisung verzweigt. Auf der anderen Seite verhält sich (a - b < 0)
wie eine jns
, die verzweigt, wenn SF = 0 ist. Daher verhalten sich diese genau, wenn OF = 1 ist.