Warum ist in Java 8 die Standardkapazität von ArrayList jetzt Null?

93

Soweit ich mich erinnere, ArrayListbetrug die Standardkapazität vor Java 8 10.

Überraschenderweise sagt der Kommentar zum Standardkonstruktor (void) immer noch: Constructs an empty list with an initial capacity of ten.

Von ArrayList.java:

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

...

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
Kevinarpe
quelle

Antworten:

105

Technisch gesehen ist es 10nicht Null, wenn Sie eine verzögerte Initialisierung des Backing-Arrays zugeben. Sehen:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

wo

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

Sie beziehen sich nur auf das anfängliche Array-Objekt mit der Größe Null, das von allen anfangs leeren ArrayListObjekten gemeinsam genutzt wird. Dh die Kapazität von 10ist träge garantiert , eine Optimierung, die auch in Java 7 vorhanden ist.

Zugegebenermaßen ist der Bauvertrag nicht ganz korrekt. Vielleicht ist dies hier die Quelle der Verwirrung.

Hintergrund

Hier ist eine E-Mail von Mike Duigou

Ich habe eine aktualisierte Version des leeren Patches ArrayList und HashMap veröffentlicht.

http://cr.openjdk.java.net/~mduigou/JDK-7143928/1/webrev/

Diese überarbeitete Implementierung führt keine neuen Felder in eine der Klassen ein. Bei ArrayList erfolgt die verzögerte Zuordnung des Hintergrundarrays nur, wenn die Liste in der Standardgröße erstellt wird. Laut unserem Leistungsanalyseteam werden ungefähr 85% der ArrayList-Instanzen in der Standardgröße erstellt, sodass diese Optimierung für die überwiegende Mehrheit der Fälle gültig ist.

Bei HashMap wird das Schwellenwertfeld kreativ verwendet, um die angeforderte Anfangsgröße zu verfolgen, bis das Bucket-Array benötigt wird. Auf der Leseseite wird der leere Kartenfall mit isEmpty () getestet. Bei der Schreibgröße wird ein Vergleich von (table == EMPTY_TABLE) verwendet, um die Notwendigkeit zu erkennen, das Bucket-Array aufzublasen. In readObject gibt es etwas mehr Arbeit, um eine effiziente Anfangskapazität auszuwählen.

Von: http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-April/015585.html

Lukas Eder
quelle
4
Laut bugs.java.com/bugdatabase/view_bug.do?bug_id=7143928 führt dies zu einer Reduzierung der Heap-Nutzung und einer Verbesserung der Antwortzeiten (die Zahlen für zwei Anwendungen werden angezeigt)
Thomas Kläger
3
@khelwood: ArrayList "meldet" seine Kapazität nicht wirklich, außer über dieses Javadoc: Es gibt keine getCapacity()Methode oder ähnliches. (Das heißt, so etwas ensureCapacity(7)ist ein No-Op für eine standardmäßig initialisierte ArrayList, also sollten wir wirklich so tun, als ob ihre anfängliche Kapazität wirklich 10 wäre.)
Ruakh
10
Schön zu graben. Die anfängliche Standardkapazität ist in der Tat nicht Null, sondern 10, wobei der Standardfall träge als Sonderfall zugewiesen wird. Sie können dies beobachten, wenn Sie einem ArrayListmit dem Konstruktor no-arg erstellten Element wiederholt Elemente hinzufügen intund dem Konstruktor keine Null übergeben , und wenn Sie die interne Arraygröße reflektierend oder in einem Debugger betrachten. Im Standardfall springt das Array von der Länge 0 auf 10 und dann auf 15, 22, gefolgt von der 1,5-fachen Wachstumsrate. Das Übergeben von Null als Anfangskapazität führt zu einem Wachstum von 0 auf 1, 2, 3, 4, 6, 9, 13, 19 ....
Stuart Marks
13
Ich bin Mike Duigou, Autor der Änderung und der zitierten E-Mail, und ich stimme dieser Nachricht zu. 🙂 Wie Stuart sagt, lag die Motivation in erster Linie in der Platzersparnis und nicht in der Leistung, obwohl es auch einen leichten Leistungsvorteil gibt, da häufig die Erstellung des Hintergrundarrays vermieden wird.
Mike Duigou
4
@assylias :; ^) nein, es hat immer noch seinen Platz, da ein Singleton emptyList()immer noch weniger Speicher verbraucht als mehrere leere ArrayListInstanzen. Es ist jetzt nur weniger wichtig und wird daher nicht an jedem Ort benötigt , insbesondere nicht an Orten mit einer höheren Wahrscheinlichkeit, Elemente zu einem späteren Zeitpunkt hinzuzufügen. Denken Sie auch daran , dass Sie manchmal wollen eine unveränderliche leere Liste und dann emptyList()ist der Weg zu gehen.
Holger
23

In Java 8 ist die Standardkapazität von ArrayList 0, bis mindestens ein Objekt zum ArrayList-Objekt hinzugefügt wird (Sie können es als verzögerte Initialisierung bezeichnen).

Die Frage ist nun, warum diese Änderung in JAVA 8 vorgenommen wurde.

Die Antwort ist, Speicherplatz zu sparen. Millionen von Array-Listenobjekten werden in Echtzeit-Java-Anwendungen erstellt. Die Standardgröße von 10 Objekten bedeutet, dass wir bei der Erstellung 10 Zeiger (40 oder 80 Byte) für das zugrunde liegende Array zuweisen und diese mit Nullen füllen. Ein leeres Array (gefüllt mit Nullen) belegt viel Speicher.

Durch die verzögerte Initialisierung wird dieser Speicherverbrauch verschoben, bis Sie die Array-Liste tatsächlich verwenden.

Weitere Informationen finden Sie im folgenden Code.

ArrayList al = new ArrayList();          //Size:  0, Capacity:  0
ArrayList al = new ArrayList(5);         //Size:  0, Capacity:  5
ArrayList al = new ArrayList(new ArrayList(5)); //Size:  0, Capacity:  0
al.add( "shailesh" );                    //Size:  1, Capacity: 10

public static void main( String[] args )
        throws Exception
    {
        ArrayList al = new ArrayList();
        getCapacity( al );
        al.add( "shailesh" );
        getCapacity( al );
    }

    static void getCapacity( ArrayList<?> l )
        throws Exception
    {
        Field dataField = ArrayList.class.getDeclaredField( "elementData" );
        dataField.setAccessible( true );
        System.out.format( "Size: %2d, Capacity: %2d%n", l.size(), ( (Object[]) dataField.get( l ) ).length );
}

Response: - 
Size:  0, Capacity:  0
Size:  1, Capacity: 10

Artikel Die Standardkapazität von ArrayList in Java 8 erläutert dies ausführlich.

Shailesh Vikram Singh
quelle
7

Wenn die allererste Operation, die mit einer ArrayList ausgeführt wird, darin besteht, addAlleine Sammlung mit mehr als zehn Elementen zu übergeben, wird jeder Aufwand, der in die Erstellung eines anfänglichen Arrays mit zehn Elementen für den Inhalt der ArrayList investiert wird, aus dem Fenster geworfen. Wenn einer ArrayList etwas hinzugefügt wird, muss getestet werden, ob die Größe der resultierenden Liste die Größe des Sicherungsspeichers überschreitet. Wenn Sie zulassen, dass der anfängliche Sicherungsspeicher die Größe Null anstelle von zehn hat, schlägt dieser Test ein weiteres Mal während der Lebensdauer einer Liste fehl, deren erste Operation ein "Hinzufügen" ist, für das das anfängliche Array mit zehn Elementen erstellt werden muss. Diese Kosten sind jedoch weniger als die Kosten für die Erstellung eines Arrays mit zehn Elementen, das nie verwendet wird.

Allerdings wäre es in einigen Kontexten möglicherweise möglich gewesen, die Leistung weiter zu verbessern, wenn eine Überladung von "addAll" aufgetreten wäre, in der angegeben wurde, wie viele Elemente (falls vorhanden) nach dem vorliegenden wahrscheinlich zur Liste hinzugefügt würden und welche Verwenden Sie dies, um das Zuordnungsverhalten zu beeinflussen. In einigen Fällen hat Code, der die letzten Elemente zu einer Liste hinzufügt, eine ziemlich gute Vorstellung davon, dass die Liste darüber hinaus keinen Platz mehr benötigt. Es gibt viele Situationen, in denen eine Liste einmal ausgefüllt und danach nie mehr geändert wird. Wenn der Code zu diesem Zeitpunkt weiß, dass die endgültige Größe einer Liste 170 Elemente beträgt, hat sie 150 Elemente und einen Hintergrundspeicher der Größe 160,

Superkatze
quelle
Sehr gute Punkte über addAll(). Dies ist eine weitere Möglichkeit, die Effizienz rund um den ersten Malloc zu verbessern.
Kevinarpe
@kevinarpe: Ich wünschte, Javas Bibliothek hätte mehr Möglichkeiten für Programme entwickelt, um anzuzeigen, wie wahrscheinlich Dinge verwendet werden. Die alte Art der Teilzeichenfolge war zum Beispiel für einige Anwendungsfälle mies, für andere jedoch hervorragend. Hätte es getrennte Funktionen für "Teilzeichenfolge, die wahrscheinlich das Original überdauern wird" und "Teilzeichenfolge, die das Original wahrscheinlich nicht überdauern wird" gegeben, und Code hätte in 90% der Fälle die richtige verwendet, hätte ich gedacht, dass diese Funktionen entweder die alte oder neue String-Implementierung.
Supercat
3

Die Frage ist 'warum?'.

Inspektionen der Speicherprofilerstellung (z. B. ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ) zeigen, dass leere (mit Nullen gefüllte) Arrays Tonnen von Speicher belegen.

Die Standardgröße von 10 Objekten bedeutet, dass wir bei der Erstellung 10 Zeiger (40 oder 80 Byte) für das zugrunde liegende Array zuweisen und diese mit Nullen füllen. Echte Java-Anwendungen erstellen Millionen von Array-Listen.

Die eingeführte Modifikation entfernt ^ W verschiebt diesen Speicherverbrauch bis zu dem Moment, an dem Sie die Array-Liste tatsächlich verwenden.

ya_pulser
quelle
Bitte korrigieren Sie "verbrauchen" mit "Abfall". Der von Ihnen bereitgestellte Link bedeutet nicht, dass sie überall Speicher verschlingen, nur dass Arrays mit Nullelementen den ihnen zugewiesenen Speicher überproportional verschwenden. "Verbrauchen" bedeutet, dass sie den Speicher auf magische Weise über ihre Zuordnung hinaus verwenden, was nicht der Fall ist.
Mechalynx
0

Die Standardgröße von ArrayList in JAVA 8 ist Stil 10. Die einzige in JAVA 8 vorgenommene Änderung besteht darin, dass die verbleibenden leeren Stellen in der Arrayliste nicht mit Null angegeben werden, wenn ein Codierer Elemente mit weniger als 10 hinzufügt. Das zu sagen, weil ich selbst durch diese Situation und die Sonnenfinsternis gegangen bin, hat mich dazu gebracht, mich mit dieser Änderung von JAVA 8 zu befassen.

Sie können diese Änderung rechtfertigen, indem Sie sich den folgenden Screenshot ansehen. Darin können Sie sehen, dass die ArrayList-Größe in Object [10] als 10 angegeben ist, die Anzahl der angezeigten Elemente jedoch nur 7 beträgt. Rest-Nullwertelemente werden hier nicht angezeigt. In JAVA 7 ist der folgende Screenshot mit nur einer einzigen Änderung identisch, dh es werden auch die Nullwertelemente angezeigt, für die der Codierer Code für die Behandlung von Nullwerten schreiben muss, wenn er die vollständige Array-Liste iteriert, während in JAVA 8 diese Belastung beseitigt wird der Leiter des Codierers / Entwicklers.

Screenshot-Link.

TechTeddy
quelle
0

Nach der obigen Frage habe ich das ArrayList-Dokument von Java 8 durchgesehen. Ich habe festgestellt, dass die Standardgröße immer noch nur 10 ist.

Siehe unten

Rahul Maurya
quelle