Der übliche Konstruktor von ArrayList
ist:
ArrayList<?> list = new ArrayList<>();
Es gibt aber auch einen überladenen Konstruktor mit einem Parameter für seine Anfangskapazität:
ArrayList<?> list = new ArrayList<>(20);
Warum ist es nützlich, eine ArrayList
mit einer anfänglichen Kapazität zu erstellen, wenn wir sie nach Belieben anhängen können?
java
data-structures
arraylist
capacity
rauben
quelle
quelle
Antworten:
Wenn Sie im Voraus wissen, wie groß die Größe
ArrayList
sein wird, ist es effizienter, die anfängliche Kapazität anzugeben. Wenn Sie dies nicht tun, muss das interne Array wiederholt neu zugewiesen werden, wenn die Liste wächst.Je größer die endgültige Liste ist, desto mehr Zeit sparen Sie, indem Sie die Neuzuweisungen vermeiden.
Das Einfügen von
n
Elementen auf der Rückseite einesArrayList
wird jedoch auch ohne vorherige Zuweisung garantiert die gesamteO(n)
Zeit in Anspruch nehmen . Mit anderen Worten, das Anhängen eines Elements ist eine amortisierte Operation mit konstanter Zeit. Dies wird erreicht, indem jede Neuzuweisung die Größe des Arrays exponentiell erhöht, typischerweise um einen Faktor von1.5
. Mit diesem Ansatz kann die Gesamtzahl der Operationen angezeigt werdenO(n)
.quelle
O(n log n)
würdelog n
Arbeitszeiten machenn
. Das ist eine grobe Überschätzung (obwohl technisch korrekt mit großem O, da es eine Obergrenze ist). Es kopiert insgesamt s + s * 1,5 + s * 1,5 ^ 2 + ... + s * 1,5 ^ m (so dass s * 1,5 ^ m <n <s * 1,5 ^ (m + 1)) Elemente. Ich bin nicht gut in Summen, daher kann ich Ihnen nicht die genaue Mathematik auf den Kopf stellen (für die Größenänderung von Faktor 2 ist es 2n, also kann es 1,5n sein, geben oder nehmen Sie eine kleine Konstante), aber das tut es nicht. ' Nehmen Sie nicht zu viel Schielen, um zu sehen, dass diese Summe höchstens einen konstanten Faktor größer als n ist. Es werden also O (k * n) Kopien benötigt, was natürlich O (n) ist.Da
ArrayList
es sich um eine Array- Datenstruktur mit dynamischer Größenänderung handelt, bedeutet dies, dass sie als Array mit einer anfänglichen (Standard-) festen Größe implementiert ist. Wenn dies gefüllt ist, wird das Array auf ein doppelt großes Array erweitert. Dieser Vorgang ist kostspielig, daher möchten Sie so wenig wie möglich.Wenn Sie also wissen, dass Ihre Obergrenze 20 Elemente beträgt, ist es besser, das Array mit einer Anfangslänge von 20 zu erstellen, als einen Standardwert von beispielsweise 15 zu verwenden. Ändern Sie dann die Größe auf
15*2 = 30
20 und verwenden Sie nur 20, während Sie die Zyklen für die Erweiterung verschwenden.PS - Wie AmitG sagt, ist der Expansionsfaktor implementierungsspezifisch (in diesem Fall
(oldCapacity * 3)/2 + 1
)quelle
int newCapacity = (oldCapacity * 3)/2 + 1;
Die Standardgröße von Arraylist ist 10 .
Wenn Sie also 100 oder mehr Datensätze hinzufügen, können Sie den Aufwand für die Neuzuweisung von Speicher sehen.
Wenn Sie also eine Vorstellung von der Anzahl der Elemente haben, die in Arraylist gespeichert werden, ist es besser, Arraylist mit dieser Größe zu erstellen, anstatt mit 10 zu beginnen und diese dann weiter zu erhöhen.
quelle
private static final int DEFAULT_CAPACITY = 10
Ich habe vor 2 Monaten einen Blog-Beitrag zum Thema geschrieben. Der Artikel ist für C #,
List<T>
aber JavaArrayList
hat eine sehr ähnliche Implementierung. DaArrayList
es mithilfe eines dynamischen Arrays implementiert wird, nimmt es bei Bedarf an Größe zu. Der Grund für den Kapazitätskonstruktor liegt also in Optimierungszwecken.Wenn eine dieser Größenänderungsvorgänge ausgeführt wird, kopiert die ArrayList den Inhalt des Arrays in ein neues Array, das doppelt so groß ist wie das alte. Diese Operation läuft in O (n) Zeit.
Beispiel
Hier ist ein Beispiel, wie sich die
ArrayList
Größe erhöhen würde:Die Liste beginnt also mit einer Kapazität von
10
: Wenn das 11. Element hinzugefügt wird, wird es um50% + 1
bis erhöht16
. Ab dem 17. PunktArrayList
wird der Wert erneut erhöht25
und so weiter. Betrachten Sie nun das Beispiel, in dem wir eine Liste erstellen, in der die gewünschte Kapazität bereits als bekannt ist1000000
. Wenn Sie denArrayList
Konstruktor ohne Größe erstellen, werdenArrayList.add
1000000
Zeiten aufgerufen , die normalerweise O (1) oder O (n) beim Ändern der Größe benötigen.Vergleichen Sie dies mit dem Konstruktor und rufen Sie dann auf,
ArrayList.add
was garantiert in O (1) ausgeführt wird .Java vs C #
Java ist wie oben und beginnt bei
10
und erhöht jede Größenänderung bei50% + 1
. C # beginnt bei4
und steigt viel aggressiver an und verdoppelt sich bei jeder Größenänderung. Das1000000
Beispiel von oben für C # verwendet3097084
Operationen.Verweise
quelle
Durch Festlegen der Anfangsgröße einer ArrayList, z. B. auf
ArrayList<>(100)
, wird die Häufigkeit der Neuzuweisung des internen Speichers verringert.Beispiel:
Wie Sie im obigen Beispiel sehen,
ArrayList
kann ein bei Bedarf erweitert werden. Dies zeigt Ihnen jedoch nicht, dass sich die Größe der Arraylist normalerweise verdoppelt (beachten Sie jedoch, dass die neue Größe von Ihrer Implementierung abhängt). Folgendes wird aus Oracle zitiert :Wenn Sie keine Ahnung haben, welchen Bereich Sie halten werden, ist das Einstellen der Größe wahrscheinlich keine gute Idee. Wenn Sie jedoch einen bestimmten Bereich im Auge haben, erhöht das Einstellen einer anfänglichen Kapazität die Speichereffizienz .
quelle
ArrayList kann viele Werte enthalten. Wenn Sie große anfängliche Einfügungen vornehmen, können Sie ArrayList anweisen, zunächst einen größeren Speicher zuzuweisen, um keine CPU-Zyklen zu verschwenden, wenn versucht wird, mehr Speicherplatz für das nächste Element zuzuweisen. Daher ist es effizienter, am Anfang etwas Platz zuzuweisen.
quelle
Dies dient dazu, mögliche Anstrengungen zur Neuzuweisung für jedes einzelne Objekt zu vermeiden.
intern
new Object[]
erstellt wird.JVM muss erstellt werden,
new Object[]
wenn Sie ein Element zur Arrayliste hinzufügen. Wenn Sie nicht über Code haben (jede algo Sie denken) für die Neuaufteilung jedes Mal dann , wenn Sie rufenarraylist.add()
dannnew Object[]
muss erstellt werden , die sinnlos ist , und wir verlieren Zeit Größe für jede von 1 zu erhöhen und jede Objekte hinzugefügt werden. Daher ist es besser, die GrößeObject[]
mit der folgenden Formel zu erhöhen .(JSL hat die unten angegebene Forcasting-Formel für dynamisch wachsende Arraylisten verwendet, anstatt jedes Mal um 1 zu wachsen. Da das Wachstum von JVM Anstrengungen erfordert)
quelle
add
- es verwendet bereits intern eine Wachstumsformel. Daher wird die Frage nicht beantwortet.int newCapacity = (oldCapacity * 3)/2 + 1;
der, der in der ArrayList-Klasse vorhanden ist. Denken Sie immer noch, dass es unbeantwortet bleibt?ArrayList
der amortisierten Umverteilung erfolgt auf jeden Fall mit jedem Wert für die anfängliche Kapazität. Und die Frage ist: Warum überhaupt einen nicht standardmäßigen Wert für die Anfangskapazität verwenden? Außerdem: "Lesen zwischen den Zeilen" ist in einer technischen Antwort nicht erwünscht. ;-)Ich denke, jede ArrayList wird mit einem Init-Kapazitätswert von "10" erstellt. Wenn Sie also eine ArrayList erstellen, ohne die Kapazität im Konstruktor festzulegen, wird sie mit einem Standardwert erstellt.
quelle
Ich würde sagen, es ist eine Optimierung. ArrayList ohne anfängliche Kapazität hat ~ 10 leere Zeilen und wird beim Hinzufügen erweitert.
Um eine Liste mit genau der Anzahl der Elemente zu erhalten, müssen Sie trimToSize () aufrufen.
quelle
Nach meiner Erfahrung mit
ArrayList
ist die Angabe einer Anfangskapazität ein guter Weg, um Umverteilungskosten zu vermeiden. Aber es ist eine Einschränkung. Alle oben genannten Vorschläge besagen, dass man die Anfangskapazität nur dann bereitstellen sollte, wenn eine grobe Schätzung der Anzahl der Elemente bekannt ist. Wenn wir jedoch versuchen, eine anfängliche Kapazität ohne Ahnung anzugeben, wird die reservierte und nicht verwendete Speichermenge eine Verschwendung sein, da sie möglicherweise nie benötigt wird, sobald die Liste mit der erforderlichen Anzahl von Elementen gefüllt ist. Was ich damit sagen möchte, ist, dass wir am Anfang pragmatisch sein können, während wir Kapazitäten zuweisen, und dann einen intelligenten Weg finden, um die erforderliche minimale Kapazität zur Laufzeit zu ermitteln. ArrayList bietet eine Methode namensensureCapacity(int minCapacity)
. Aber dann hat man einen klugen Weg gefunden ...quelle
Ich habe ArrayList mit und ohne initialCapacity getestet und habe ein überraschendes Ergebnis erhalten.
Wenn ich LOOP_NUMBER auf 100.000 oder weniger setze, ist das Ergebnis, dass das Setzen von initialCapacity effizient ist.
Wenn ich LOOP_NUMBER auf 1.000.000 setze, ändert sich das Ergebnis zu:
Schließlich konnte ich nicht herausfinden, wie es funktioniert?!
Beispielcode:
Ich habe unter Windows 8.1 und JDK1.7.0_80 getestet
quelle