Сохраняются ли интернированные строки при сериализации?
Если у меня есть большой объектный граф, содержащий много повторяющихся строк, есть ли польза в интернировании()строк перед их сериализацией? Уменьшит ли это объем передаваемых данных? Будут ли строки совместно использовать указатели на принимающем конце?
Я предполагаю, что строки будут де-обмануты перед отправкой, таким образом уменьшая размер данных, и что все они будут представлены одним и тем же объектом на принимающей стороне, но что они фактически не будут интернированы на приемный конец. (это означает, что при каждой транзакции сериализации будет создан один новый экземпляр строки)
4 ответа:
ObjectOutputStream отслеживает график объектов (до сброса), один объект записывается только один раз, даже если он достигается с помощью нескольких ссылок. Сокращение объектов путем интернирования определенно приведет к сокращению байтов.
На принимающей стороне создается тот же граф объектов, поэтому один экземпляр строки на отправляющей стороне становится одним экземпляром строки на принимающей стороне.
Это достаточно легко проверить:
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>
для создания "временного" пула стажеров.
Вы можете использовать это расширение
Обратите внимание, что используется неObjectOutputStream
, которое реализует дедупликациюString
. Выходные данные должны быть совместимы с оригинальной версией (не тестировалась), поэтому никаких специальныхObjectInputStream
не требуется.String.intern()
, а частное и временное внутреннееMap
, поэтому ваше Пермгенпространство не затоплено.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 символов и не сталкивался с какой-либо проблемой.