【问题标题】:Java serialization with empty and substrings带有空字符串和子字符串的 Java 序列化
【发布时间】:2016-01-12 10:50:18
【问题描述】:

查看了实现,但无法想到对此的解释,但也许这里有人会知道。

public static void main(String[] args) throws Exception {
    List<String> emptyStrings = new ArrayList<String>();
    List<String> emptySubStrings = new ArrayList<String>();
    for (int i = 0; i < 20000; i++) {
        String actuallyEmpty = "";
        String subStringedEmpty = "                                                                 ";
        subStringedEmpty = subStringedEmpty.substring(0, 0);
        emptyStrings.add(actuallyEmpty);
        emptySubStrings.add(subStringedEmpty);
    }
    System.out.println("Substring test");
    // Write to files
    long time = System.currentTimeMillis();
    writeObjectToFile(emptyStrings, "empty.list");
    System.out.println("Time taken to write empty list " + (System.currentTimeMillis() - time));
    time = System.currentTimeMillis();
    writeObjectToFile(emptySubStrings, "substring.list");
    System.out.println("Time taken to write substring list " + (System.currentTimeMillis() - time));
    //Read from files
    time = System.currentTimeMillis();
    List<String> readEmptyString = readObjectFromFile("empty.list");
    System.out.println("Time taken to read empty list " + (System.currentTimeMillis() - time));
    time = System.currentTimeMillis();
    List<String> readEmptySubStrings = readObjectFromFile("substring.list");
    System.out.println("Time taken to read substring list " + (System.currentTimeMillis() - time));
}

private static void writeObjectToFile(Object o, String file) throws Exception {
    FileOutputStream out = new FileOutputStream(file);
    ObjectOutputStream oout = new ObjectOutputStream(out);
    oout.writeObject(o);
    oout.flush();
    oout.close();
}

private static <T> T readObjectFromFile(String file) throws Exception {
    ObjectInputStream ois = null;
    try {
        ois = new ObjectInputStream(new FileInputStream(file));
        return (T) ois.readObject();
    } finally {
        ois.close();
    }
}

这两个列表最终包含 20,000 个空字符串(一个列表包含 "" 空字符串,另一个包含由 substring(0,0) 生成的空字符串)。但是,如果您检查生成的序列化文件(empty.list 和 substring.list)的大小,您会注意到 empty.list 包含更多的数据。

我注意到,取消序列化这些子字符串对象的远程 EJB 的调用者似乎也存在严重的性能问题。

【问题讨论】:

  • 您可能想在某处添加一个实际问题。
  • 可能是实习生与非实习生的案例。如果您在空子字符串上调用 String#intern() 会发生什么?
  • 你能仔细检查一下它是不是更大的空列表吗?
  • 是的,原来的 empty.list 更大,似乎对实习对象的序列化引用大于新的空字符串对象?但尚未证实这一点。

标签: java string serialization substring


【解决方案1】:

列表的大小不同,因为 java 使用一种机制来存储对同一对象的多个引用,如下所述:

对其他对象的引用(瞬态或静态字段除外) 导致这些对象也被写入。多个引用 单个对象使用参考共享机制进行编码,以便 对象的图形可以恢复到与 原来是写的。

ObjectOutputStream

如果你查看生成的序列化文件,你会看到:

内部有 1 个空字符串:

空列表:

ac ed 00 05 73 72 00 13 6a 61 76 61 2e 75 74 69
6c 2e 41 72 72 61 79 4c 69 73 74 78 81 d2 1d 99
c7 61 9d 03 00 01 49 00 04 73 69 7a 65 78 70 00
00 00 01 77 04 00 00 00 01 74 00 00 78

字符串“”对应最后三个字节(00 00 78

子字符串列表

ac ed 00 05 73 72 00 13 6a 61 76 61 2e 75 74 69
6c 2e 41 72 72 61 79 4c 69 73 74 78 81 d2 1d 99
c7 61 9d 03 00 01 49 00 04 73 69 7a 65 78 70 00
00 00 01 77 04 00 00 00 01 74 00 00 78

请注意,使用一个元素生成的文件是相同的。

但是如果我们想多次添加同一个对象,我们就会面临其他的行为。 用该字符串的 2 倍查找相应的文件。

空列表:

ac ed 00 05 73 72 00 13 6a 61 76 61 2e 75 74 69
6c 2e 41 72 72 61 79 4c 69 73 74 78 81 d2 1d 99
c7 61 9d 03 00 01 49 00 04 73 69 7a 65 78 70 00
00 00 02 77 04 00 00 00 02 74 00 00 71 00 7e 00
02 78

子字符串列表

ac ed 00 05 73 72 00 13 6a 61 76 61 2e 75 74 69
6c 2e 41 72 72 61 79 4c 69 73 74 78 81 d2 1d 99
c7 61 9d 03 00 01 49 00 04 73 69 7a 65 78 70 00
00 00 02 77 04 00 00 00 02 74 00 00 74 00 00 78

请注意,子字符串继续“正常”,两个不相关的字符串具有不同的引用。但是空有一些额外的字节来处理相同引用的问题。

子字符串中的六个字节 (00 00 74 00 00 78) 与空列表中的八个字节 (00 00 71 00 7e 00 02 78)

这是错误的,因为您添加的每个重复字符串都会添加更多额外的字节。因此,当您填满您的 arrayList 时,将会有很多额外的字节,以便可以按照原始方式进行重构。

如果你想知道为什么会有这种共享机制,我建议你看看这个问题:

What is the meaning of reference sharing in Serialization? How Enums are Serialized?

【讨论】:

  • 注意:这似乎只是因为空字符串在序列化时特别小。对于大多数对象,引用共享会导致共享文件变小,而不是未共享文件。
【解决方案2】:

empty.list 包含一个 String 对象和很多对它的引用。

substring.list 包含 2000 个字符串对象,它们的内容都是相等的。

你可以通过对字符串进行 intern() 来“修复”这个问题。

private void verify(String name, Supplier<String> stringSupplier) throws IOException, ClassNotFoundException {
    List<String> inputStrings = new ArrayList<String>();
    inputStrings.add(stringSupplier.get());
    inputStrings.add(stringSupplier.get());

    ByteArrayOutputStream boas = new ByteArrayOutputStream();
    ObjectOutputStream emptyOut = new ObjectOutputStream(boas);
    emptyOut.writeObject(inputStrings);
    emptyOut.flush();

    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(boas.toByteArray()));
    List<String> returnedStrings = (List<String>)ois.readObject();

    if(returnedStrings.get(0) == returnedStrings.get(1)) {
        System.out.println(name + " contains the same object");
    } else {
        System.out.println(name + " contains DIFFERENT objects");
    }
}

@Test
public void test() throws IOException, ClassNotFoundException {
    verify("empty string", new Supplier<String>() {
        @Override
        public String get() {
            return "";
        }
    });
    verify("sub string", new Supplier<String>() {
        @Override
        public String get() {
            String data = "  ";
            return data.substring(0, 0);
        }
    });
    verify("intern()ed substring", new Supplier<String>() {
        @Override
        public String get() {
            String data = "  ";
            return data.substring(0, 0).intern();
        }
    });
}

【讨论】:

  • 问题说 empty.list 包含更多的数据,这也与我的预期相反。
  • 我也注意到了这一点。我必须研究序列化/反序列化过程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-20
  • 1970-01-01
  • 2020-11-20
  • 2012-03-06
  • 1970-01-01
  • 1970-01-01
  • 2023-03-04
相关资源
最近更新 更多