序列化时是否保留Interned Strings?

时间:2011-10-21 19:22:04

标签: java serializable string-interning

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

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

4 个答案:

答案 0 :(得分:6)

测试很容易:

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()本身,因为您可能不希望所有这些字符串都在正常的实习池中 - 但您总是可以使用HashSet<String>来创建“临时”实习生池。

答案 1 :(得分:4)

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

在接收端,重新创建相同的对象图,因此发送端的一个字符串实例成为接收端的一个字符串实例。

答案 2 :(得分:2)

您可以使用此ObjectOutputStream增强功能实现String的重复数据删除。输出应该与原始版本兼容(未测试),因此不需要特殊的ObjectInputStream

请注意,不使用String.intern(),而是使用私有和临时内部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;
    }
}

答案 3 :(得分: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个字符的字符串并且没有遇到任何问题。