文字列に「」を追加するとメモリが節約されるのはなぜですか?


193

大量のデータを含む変数を使用しましたString data。この文字列の一部を次のように使用したいと思います。

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

(メモリビジュアライザを使用して)数時間デバッグした後、objectsフィールドにsmallpartはのすべてのデータが記憶されていることがわかりましたがdata、部分文字列しか含まれていませんでした。

コードを次のように変更した場合:

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

..問題は解決しました!現在、私のアプリケーションはメモリをほとんど使用していません!

そんなことがあるものか?誰かがこれを説明できますか?this.smallpartはデータを参照し続けたと思いますが、なぜですか?

更新:では、 どうすれば大きな文字列をクリアできますか?data = new String(data.substring(0,100))はそれをしますか?


以下の最終的な意図について詳しくお読みください:最初に大きな紐はどこから来たのですか?ファイルやデータベースのCLOBなどから読み取る場合は、解析中に必要なものだけを読み取るのが最適です。
PSpeed 2010年

4
すばらしい...私は4〜5年以上Javaで働いていますが、これはまだ新しいことです:)。情報の仲間に感謝します。
Parth

1
使用には微妙な点がありnew String(String)ます。stackoverflow.com/a/390854/8946を参照してください。
Lawrence Dol 2013年

回答:


159

以下を実行します。

data.substring(x, y) + ""

新しい(小さい)Stringオブジェクトを作成し、substring()によって作成されたStringへの参照を破棄することで、これのガベージコレクションを有効にします。

理解しておくべき重要なことはsubstring()既存の文字列、つまり、元の文字列の基礎となる文字配列にウィンドウを提供することです。したがって、元の文字列と同じメモリを消費します。これは状況によっては有利な場合がありますが、部分文字列を取得して元の文字列を破棄したい場合は問題があります(ご存知のとおり)。

詳細については、JDK文字列ソースのsubstring()メソッドを参照してください。

EDITは:、あなたのメモリ消費量を削減する部分文字列から新しい文字列を構築し、あなたの補足質問に答えるために提供元の文字列にあなたの参照をビン。

注(2013年1月)。上記の動作は、Java 7u6で変更されました。flyweightパターンは使用されなくなり、substring()期待どおりに機能します。


89
これは、String(String)コンストラクター(つまり、Stringを入力として受け取るStringコンストラクター)が役立つ非常に数少ないケースの1つです。new String(data.substring(x, y))事実上、appendingと同じことを""行いますが、意図がいくらか明確になります。
Joachim Sauer

3
正確には、部分文字列はvalue元の文字列の属性を使用します。それがリファレンスが保持されている理由だと思います。
Valentin Rocher

@Bishiboosh-はい、そうです。実装の特殊性を明らかにしたくありませんでしたが、まさにそれが起こっています。
Brian Agnew

5
技術的には、実装の詳細です。しかし、それでもいらいらし、多くの人を引き付けます。
Brian Agnew

1
弱い参照などを使用してJDKでこれを最適化することは可能ですか?私がこの文字[]を必要とする最後の人であり、ほんの少しだけ必要な場合は、内部で使用する新しい配列を作成します。
WW。

28

のソースを見ると、次のようsubstring(int, int)に返されることがわかります。

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

valueオリジナルはどこですchar[]。したがって、新しいStringを取得しますが、同じ基礎を持っていchar[]ます。

するとdata.substring() + ""新しい基礎となる新しい文字列を取得しますchar[]

実際、あなたのユースケースは、String(String)コンストラクターを使用すべき唯一の状況です:

String tiny = new String(huge.substring(12,18));

1
使用には微妙な点がありnew String(String)ます。stackoverflow.com/a/390854/8946を参照してください。
Lawrence Dol 2013年

17

を使用してもsubstring、実際には新しい文字列は作成されません。それでも、オフセットとサイズの制約がある元の文字列を参照します。

したがって、元の文字列を収集できるようにするには、新しい文字列を作成する必要があります(new Stringまたはを使用して)。


5

this.smallpartは引き続きデータを参照していると思いますが、なぜですか?

Java文字列はchar配列、開始オフセットと長さ(およびキャッシュされたhashCode)で構成されるため。substring()オリジナルのchar配列を共有し、単に異なるオフセットや長さフィールドを持つ新しいStringオブジェクトの作成などの一部のString操作。これは、Stringのchar配列が一度作成されると変更されることがないため機能します。

これにより、多くの部分文字列が重複する部分を複製せずに同じ基本文字列を参照する場合に、メモリを節約できます。お気づきのように、状況によっては、不要になったデータがガベージコレクションされないようにすることができます。

これを修正する「正しい」方法はnew String(String)コンストラクタです。

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

ところで、全体的な最善の解決策は、最初に非常に大きな文字列を使用せず、一度に数KBの小さなチャンクで入力を処理しないことです。


使用には微妙な点がありnew String(String)ます。stackoverflow.com/a/390854/8946を参照してください。
Lawrence Dol 2013年

5

Javaでは文字列は互換性のあるオブジェクトであり、文字列が作成されると、ガベージコレクターによってクリーンアップされるまでメモリに残ります(このクリーンアップは当然のことではありません)。

substringメソッドを呼び出すと、Javaはまったく新しい文字列を作成せず、元の文字列内にある範囲の文字を格納するだけです。

したがって、次のコードで新しい文字列を作成すると、

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

結果を空の文字列と連結すると、実際には新しい文字列が作成されます。それが理由です。


3

1997年にjwzによって文書化されたように

巨大な文字列がある場合は、その文字列のsubstring()を引き出し、部分文字列を保持して、長い文字列が不要になる(つまり、部分文字列の寿命が長くなる)ようにします。離れて。


2

要約すると、少数の大きな文字列から多数の部分文字列を作成する場合は、

   String subtring = string.substring(5,23)

大きな文字列を格納するためにスペースのみを使用するため、大きな文字列の損失から、ほんの一握りの小さな文字列を抽出する場合、

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

大きな文字列は不要になったときに再利用できるため、メモリ使用量を抑えられます。

呼び出すnew Stringということは、元の文字列への参照ではなく、実際に新しい文字列を取得していることを思い出させるのに役立ちます。


使用には微妙な点がありnew String(String)ます。stackoverflow.com/a/390854/8946を参照してください。
Lawrence Dol 2013年

2

まず、呼び出しjava.lang.String.substringは、元のString配列の重要な部分をコピーする代わりに、オフセットと長さを使用して、元のウィンドウに新しいウィンドウ作成します

substringメソッドを詳しく見てみると、文字列コンストラクターの呼び出しString(int, int, char[])char[]文字列を表す全体を呼び出していることがわかります。つまり、部分文字列は元の文字列と同じ量のメモリを占有します

わかりましたが、なぜ+ ""それがない場合よりも少ないメモリが必要になるのですか?

行う+には、stringsを介して実行されたStringBuilder.appendメソッド呼び出し。AbstractStringBuilderクラスでこのメソッドの実装を見ると、最終的にarraycopy本当に必要な部分(substring)でそれが行われることがわかります。

他の回避策はありますか?

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

0

文字列に ""を追加する、メモリを節約できる場合あります。

本全体、100万文字を含む巨大な文字列があるとします。

次に、本の章を部分文字列として含む20個の文字列を作成します。

次に、すべての段落を含む1000個の文字列を作成します。

次に、すべての文を含む10,000個の文字列を作成します。

次に、すべての単語を含む100,000個の文字列を作成します。

まだ1,000,000文字しか使用していません。各章、段落、文、単語に「」を追加すると、5,000,000文字が使用されます。

もちろん、本全体から1つの単語のみを抽出する場合はまったく異なります。本全体がガベージコレクションされる可能性がありますが、その1つの単語が参照を保持しているためではありません。

また、100万文字の文字列があり、両端のタブとスペースを削除して、たとえば10回呼び出して部分文字列を作成する場合も、これは異なります。Javaが機能する方法または機能する方法では、毎回100万文字をコピーする必要がありません。妥協があり、妥協が何であるかを知っているのは良いことです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.