多くの重複文字列を含む大きなオブジェクト グラフがある場合、シリアル化する前に文字列をインターン()する利点はありますか? これにより、転送されるデータの量が減りますか? 文字列は受信側でポインタを共有しますか?
私の推測では、文字列は送信前に重複排除されるため、データのサイズが縮小され、受信側ではすべて同じオブジェクトで表されますが、実際には受信側ではインターンされません。(つまり、シリアル化の「トランザクション」ごとに文字列の新しいインスタンスが 1 つ作成されます)
多くの重複文字列を含む大きなオブジェクト グラフがある場合、シリアル化する前に文字列をインターン()する利点はありますか? これにより、転送されるデータの量が減りますか? 文字列は受信側でポインタを共有しますか?
私の推測では、文字列は送信前に重複排除されるため、データのサイズが縮小され、受信側ではすべて同じオブジェクトで表されますが、実際には受信側ではインターンされません。(つまり、シリアル化の「トランザクション」ごとに文字列の新しいインスタンスが 1 つ作成されます)
テストするのは簡単です:
import java.io.*;
class Foo implements Serializable {
private String x;
private String y;
public Foo(String x, String y) {
this.x = x;
this.y = y;
}
}
public class Test {
public static void main(String[] args) throws IOException {
String x = new StringBuilder("hello").append(" world").toString();
String y = "hello world";
showSerializedSize(new Foo(x, y));
showSerializedSize(new Foo(x, x));
}
private static void showSerializedSize(Foo foo) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(foo);
oos.close();
System.out.println(baos.size());
}
}
私のマシンでの結果:
86
77
そのため、重複排除は自動的には行われないようです。
String.intern()
ただし、通常のインターン プールにこれらの文字列のすべてを必要としない可能性があるため、それ自体は使用しませんが、常に aHashSet<String>
を使用して「一時的な」インターン プールを作成できます。
ObjectOutputStreamは、オブジェクトグラフを追跡し(リセットされるまで)、複数の参照を介して到達した場合でも、1つのオブジェクトが1回だけ書き込まれます。インターンによってオブジェクトを減らすと、バイトが確実に減ります。
受信側では、同じオブジェクトグラフが再作成されるため、送信側の1つの文字列インスタンスが受信側の1つの文字列インスタンスになります。
ObjectOutputStream
の重複排除を実装するこの拡張機能を使用できますString
。出力は元のバージョン (テストされていない) と互換性があるはずなので、特別なObjectInputStream
ことは必要ありません。
は使用されていませんString.intern()
が、プライベートで一時的な internalMap
であるため、PermGenSpace がフラッディングされていないことに注意してください。
public class StringPooledObjectOutputStream extends ObjectOutputStream {
private Map<String, String> stringPool = new HashMap<String, String>();
public StringPooledObjectOutputStream(OutputStream out) throws IOException {
super(out);
enableReplaceObject(true);
}
@Override
protected Object replaceObject(Object obj) throws IOException {
if( !(obj instanceof String) )
return super.replaceObject(obj);
String str = (String)obj;
String replacedStr = stringPool.get(str);
if( replacedStr == null ){
replacedStr = (String)super.replaceObject(str);
stringPool.put(replacedStr, replacedStr);
}
return replacedStr;
}
}
シリアル化する前に、文字列をインターンする利点はないようです。少なくとも、これはシリアル化のために何も変更しません。アプリケーションのメモリを削減するのに役立つ場合があります。
受信側では、最低レベルまたは同等readUTF()
のObjectOutPutStream
ものが呼び出され、呼び出しごとに新しい文字列が割り当てられます。クラスが外部化可能な場合readUTF().intern()
、受信側でメモリを節約できます。私自身この方法を使用したところ、クライアント アプリケーションのメモリ使用量が 50% 以上削減されました。
ただし、一意の文字列が多数ある場合intern()
は、PermGen を使用するため、メモリ不足の問題が発生する可能性があることに注意してください。参照:
http://www.onkarjoshi.com/blog/213/6-things-to-remember-about-Saving-memory-with-the-string-intern-method/
10 文字未満の文字列のみをインターンし、問題に直面していません。