如果我有包含许多重复字符串的大型对象图,那么在序列化字符串之前对字符串进行实习()是否有好处?这会减少传输的数据量吗?字符串会在接收端共享指针吗?
我的猜测是,字符串会在发送之前进行重复数据删除,从而减少数据的大小,并且它们都将在接收端由同一个对象表示,但它们实际上不会在接收端被实习。(意味着在每个序列化“事务”上都会创建一个新的字符串实例)
如果我有包含许多重复字符串的大型对象图,那么在序列化字符串之前对字符串进行实习()是否有好处?这会减少传输的数据量吗?字符串会在接收端共享指针吗?
我的猜测是,字符串会在发送之前进行重复数据删除,从而减少数据的大小,并且它们都将在接收端由同一个对象表示,但它们实际上不会在接收端被实习。(意味着在每个序列化“事务”上都会创建一个新的字符串实例)
测试很容易:
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 跟踪对象图(直到重置),一个对象只被写入一次,即使它是通过多个引用到达的。通过实习减少对象肯定会减少字节。
在接收端,同样的对象图被重新创建,所以发送端的一个字符串实例变成接收端的一个字符串实例。
您可以使用此增强功能ObjectOutputStream
来实现String
. 输出应该与原始版本兼容(未测试),因此不需要特殊ObjectInputStream
。
请注意,这不是String.intern()
使用的,而是私有的和临时的 internal Map
,因此您的 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 个字符并且没有遇到任何问题的字符串。