4

如果我有包含许多重复字符串的大型对象图,那么在序列化字符串之前对字符串进行实习()是否有好处?这会减少传输的数据量吗?字符串会在接收端共享指针吗?

我的猜测是,字符串会在发送之前进行重复数据删除,从而减少数据的大小,并且它们都将在接收端由同一个对象表示,但它们实际上不会在接收端被实习。(意味着在每个序列化“事务”上都会创建一个新的字符串实例)

4

4 回答 4

7

测试很容易:

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>创建一个“临时”实习生池。

于 2011-10-21T19:27:15.097 回答
5

ObjectOutputStream 跟踪对象图(直到重置),一个对象只被写入一次,即使它是通过多个引用到达的。通过实习减少对象肯定会减少字节。

在接收端,同样的对象图被重新创建,所以发送端的一个字符串实例变成接收端的一个字符串实例。

于 2011-10-21T20:08:35.490 回答
2

您可以使用此增强功能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;
    }
}
于 2011-10-21T20:53:29.993 回答
0

在序列化之前,似乎没有对字符串进行实习的任何好处。至少这不会改变序列化的任何内容。它可能有助于减少应用程序的内存。

在最低级别的接收方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 个字符并且没​​有遇到任何问题的字符串。

于 2011-10-21T21:07:24.293 回答