Warum spart das Anhängen von "" an einen String Speicherplatz?

193

Ich habe beispielsweise eine Variable mit vielen Daten verwendet String data. Ich wollte einen kleinen Teil dieser Zeichenfolge folgendermaßen verwenden:

this.smallpart = data.substring(12,18);

Nach einigen Stunden des Debuggens (mit einem Speichervisualisierer) stellte ich fest, dass sich das smallpartObjektfeld alle Daten von erinnerte data, obwohl es nur die Teilzeichenfolge enthielt.

Als ich den Code geändert habe in:

this.smallpart = data.substring(12,18)+""; 

..das Problem wurde gelöst! Jetzt verbraucht meine Anwendung nur noch sehr wenig Speicher!

Wie ist das möglich? Kann jemand das erklären? Ich denke, this.smallpart bezog sich immer wieder auf Daten, aber warum?

UPDATE: Wie kann ich dann den großen String löschen? Wird data = new String (data.substring (0,100)) das tun?

hsmit
quelle
Lesen Sie weiter unten mehr über Ihre ultimative Absicht: Woher kommt die große Saite überhaupt? Wenn Sie aus einem Datei- oder Datenbank-CLOB oder etwas anderem lesen, ist es rundum optimal, nur das zu lesen, was Sie beim Parsen benötigen.
PSpeed
4
Erstaunlich ... Ich arbeite seit mehr als 4 bis 5 Jahren in Java, das ist immer noch neu für mich :). danke für die info bro.
Parth
1
Die Verwendung ist subtil new String(String). Siehe stackoverflow.com/a/390854/8946 .
Lawrence Dol

Antworten:

159

Folgendes tun:

data.substring(x, y) + ""

Erstellt ein neues (kleineres) String-Objekt und wirft den Verweis auf den von substring () erstellten String weg, wodurch die Speicherbereinigung dieses Objekts ermöglicht wird.

Es ist wichtig zu wissen, dass substring()ein Fenster zu einem vorhandenen String angezeigt wird - oder vielmehr zu dem Zeichenarray, das dem ursprünglichen String zugrunde liegt. Daher wird derselbe Speicher wie der ursprüngliche String belegt. Dies kann unter bestimmten Umständen vorteilhaft sein, ist jedoch problematisch, wenn Sie einen Teilstring erhalten und den ursprünglichen String entsorgen möchten (wie Sie herausgefunden haben).

Werfen Sie einen Blick auf die Methode substring () in der JDK String Quelle für weitere Informationen.

BEARBEITEN: Um Ihre Zusatzfrage zu beantworten, wird durch das Erstellen eines neuen Strings aus dem Teilstring der Speicherverbrauch reduziert, sofern Sie keine Verweise auf den ursprünglichen String enthalten.

HINWEIS (Januar 2013). Das obige Verhalten hat sich in Java 7u6 geändert . Das Fliegengewichtsmuster wird nicht mehr verwendet und substring()funktioniert wie erwartet.

Brian Agnew
quelle
89
Dies ist einer der wenigen Fälle, in denen der String(String)Konstruktor (dh der String-Konstruktor, der einen String als Eingabe verwendet) nützlich ist: Er new String(data.substring(x, y))macht effektiv dasselbe wie das Anhängen "", macht jedoch die Absicht etwas klarer.
Joachim Sauer
3
Um genau zu sein, verwendet der Teilstring das valueAttribut des ursprünglichen Strings. Ich denke, deshalb wird die Referenz beibehalten.
Valentin Rocher
@Bishiboosh - ja, das stimmt. Ich wollte die Besonderheiten der Implementierung nicht offenlegen, aber genau das passiert.
Brian Agnew
5
Technisch ist es ein Implementierungsdetail. Aber es ist trotzdem frustrierend und fängt viele Leute auf.
Brian Agnew
1
Ich frage mich, ob es möglich ist, dies im JDK mithilfe schwacher Referenzen oder dergleichen zu optimieren. Wenn ich die letzte Person bin, die dieses Zeichen [] benötigt, und ich nur ein bisschen davon benötige, erstellen Sie ein neues Array, das ich intern verwenden kann.
WW.
28

Wenn Sie sich die Quelle von ansehen substring(int, int), werden Sie feststellen, dass sie zurückkehrt:

new String(offset + beginIndex, endIndex - beginIndex, value);

Wo valueist das Original char[]? Sie erhalten also einen neuen String mit demselben Basiswert char[].

Wenn Sie dies tun, erhalten data.substring() + ""Sie einen neuen String mit einem neuen Basiswert char[].

Tatsächlich ist Ihr Anwendungsfall die einzige Situation, in der Sie den String(String)Konstruktor verwenden sollten:

String tiny = new String(huge.substring(12,18));
Pascal Thivent
quelle
1
Die Verwendung ist subtil new String(String). Siehe stackoverflow.com/a/390854/8946 .
Lawrence Dol
17

Wenn Sie verwenden substring, wird keine neue Zeichenfolge erstellt. Es bezieht sich immer noch auf Ihre ursprüngliche Zeichenfolge mit einer Versatz- und Größenbeschränkung.

Damit Ihre ursprüngliche Zeichenfolge erfasst werden kann, müssen Sie eine neue Zeichenfolge erstellen (mit new Stringoder was Sie haben).

Chris Jester-Young
quelle
5

Ich denke, this.smallpart bezog sich immer wieder auf Daten, aber warum?

Da Java-Zeichenfolgen aus einem char-Array, einem Startoffset und einer Länge (und einem zwischengespeicherten Hashcode) bestehen. Einige String-Operationen wie das substring()Erstellen eines neuen String-Objekts, das das char-Array des Originals gemeinsam nutzt und einfach unterschiedliche Offset- und / oder Längenfelder aufweist. Dies funktioniert, weil das char-Array eines Strings nach seiner Erstellung niemals geändert wird.

Dies kann Speicherplatz sparen, wenn viele Teilzeichenfolgen auf dieselbe Basiszeichenfolge verweisen, ohne überlappende Teile zu replizieren. Wie Sie bemerkt haben, kann es in einigen Situationen verhindern, dass Daten, die nicht mehr benötigt werden, durch Müll gesammelt werden.

Der "richtige" Weg, dies zu beheben, ist der new String(String)Konstruktor, dh

this.smallpart = new String(data.substring(12,18));

Übrigens wäre die insgesamt beste Lösung, zu vermeiden, dass überhaupt sehr große Strings vorhanden sind, und Eingaben in kleineren Blöcken zu verarbeiten, jeweils einige KB.

Michael Borgwardt
quelle
Die Verwendung ist subtil new String(String). Siehe stackoverflow.com/a/390854/8946 .
Lawrence Dol
5

In Java sind Zeichenfolgen unveränderliche Objekte. Sobald eine Zeichenfolge erstellt wurde, bleibt sie im Speicher, bis sie vom Garbage Colector bereinigt wird (und diese Bereinigung ist nicht selbstverständlich).

Wenn Sie die Teilstring-Methode aufrufen, erstellt Java keine völlig neue Zeichenfolge, sondern speichert lediglich einen Zeichenbereich in der ursprünglichen Zeichenfolge.

Wenn Sie also eine neue Zeichenfolge mit diesem Code erstellt haben:

this.smallpart = data.substring(12, 18) + ""; 

Sie haben tatsächlich eine neue Zeichenfolge erstellt, als Sie das Ergebnis mit der leeren Zeichenfolge verkettet haben. Deshalb.

Kico Lobo
quelle
3

Wie von jwz 1997 dokumentiert :

Wenn Sie eine große Zeichenfolge haben, ziehen Sie eine Teilzeichenfolge () heraus, halten Sie die Teilzeichenfolge fest und lassen Sie die längere Zeichenfolge zu Müll werden (mit anderen Worten, die Teilzeichenfolge hat eine längere Lebensdauer). Die zugrunde liegenden Bytes der großen Zeichenfolge werden nie gelöscht Weg.

Ken
quelle
2

Um es zusammenzufassen: Wenn Sie viele Teilzeichenfolgen aus einer kleinen Anzahl großer Zeichenfolgen erstellen, verwenden Sie

   String subtring = string.substring(5,23)

Da Sie nur den Speicherplatz zum Speichern der großen Zeichenfolgen verwenden, aber wenn Sie nur eine Handvoll kleiner Zeichenfolgen aus verlorenen großen Zeichenfolgen extrahieren, dann

   String substring = new String(string.substring(5,23));

Hält Ihren Speicherbedarf niedrig, da die großen Zeichenfolgen zurückgefordert werden können, wenn sie nicht mehr benötigt werden.

Das, was Sie aufrufen, new Stringist eine hilfreiche Erinnerung daran, dass Sie wirklich eine neue Zeichenfolge erhalten, anstatt auf die ursprüngliche zu verweisen.

mdma
quelle
Die Verwendung ist subtil new String(String). Siehe stackoverflow.com/a/390854/8946 .
Lawrence Dol
2

Erstens erstellt der Aufruf java.lang.String.substringein neues Fenster im OriginalString unter Verwendung des Versatzes und der Länge, anstatt den wesentlichen Teil des zugrunde liegenden Arrays zu kopieren.

Wenn wir uns die substringMethode genauer ansehen, werden wir einen String-Konstruktor- Aufruf bemerken String(int, int, char[])und ihn als Ganzes übergeben char[], der den String darstellt . Das bedeutet, dass der Teilstring so viel Speicher belegt wie die ursprüngliche Zeichenfolge .

Ok, aber warum + ""wird weniger Speicher benötigt als ohne?

Ein +On stringswird über einen StringBuilder.appendMethodenaufruf implementiert . Wenn Sie sich die Implementierung dieser Methode in der AbstractStringBuilderKlasse ansehen, werden Sie feststellen, dass sie endlich arraycopymit dem Teil zu tun hat, den wir gerade wirklich brauchen (der substring).

Irgendeine andere Problemumgehung?

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();
Laika
quelle
0

Das Anhängen von "" an eine Zeichenfolge spart manchmal Speicher.

Angenommen, ich habe eine riesige Zeichenfolge mit einem ganzen Buch, einer Million Zeichen.

Dann erstelle ich 20 Zeichenfolgen, die die Kapitel des Buches als Teilzeichenfolgen enthalten.

Dann erstelle ich 1000 Zeichenfolgen, die alle Absätze enthalten.

Dann erstelle ich 10.000 Zeichenfolgen, die alle Sätze enthalten.

Dann erstelle ich 100.000 Zeichenfolgen, die alle Wörter enthalten.

Ich benutze immer noch nur 1.000.000 Zeichen. Wenn Sie jedem Kapitel, Absatz, Satz und Wort "" hinzufügen, verwenden Sie 5.000.000 Zeichen.

Natürlich ist es ganz anders, wenn Sie nur ein einziges Wort aus dem ganzen Buch extrahieren, und das ganze Buch könnte Müll gesammelt werden, aber nicht, weil dieses eine Wort einen Verweis darauf enthält.

Und es ist wieder anders, wenn Sie eine Million Zeichenfolgen haben und Tabulatoren und Leerzeichen an beiden Enden entfernen, z. B. 10 Aufrufe, um eine Teilzeichenfolge zu erstellen. Durch die Funktionsweise oder Funktionsweise von Java wird vermieden, dass jedes Mal eine Million Zeichen kopiert werden. Es gibt Kompromisse, und es ist gut, wenn Sie wissen, was die Kompromisse sind.

gnasher729
quelle