【问题标题】:Why does Java serialization take up so much space?为什么Java序列化会占用这么多空间?
【发布时间】:2015-06-10 03:52:01
【问题描述】:

我尝试序列化 Byte 和 Integer 的实例,当它们在另一端收到时它们占用了多少空间,这让我感到震惊。为什么做一个 Integer 只需要 4 个字节,但序列化时却占用了 10 倍以上的字节数?我的意思是在 C++ 中,最终类有一个 64 位的类标识符,以及它的内容。脱离这个逻辑,我希望整数在序列化时占用 64 + 32 或 96 位。

import java.io.*;

public class Test {
    public static void main (String[] ar) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutput out = new ObjectOutputStream(bos);   
        out.writeObject(new Integer(32));
        byte[] yourBytes = bos.toByteArray();
        System.out.println("length: " + yourBytes.length + " bytes");
    }
}

输出:

长度:81 字节

更新:

public static void main(String[] args) throws IOException {

    {
    ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
    ObjectOutput out1 = new ObjectOutputStream(bos1);
    out1.writeObject(new Boolean(false));
    byte[] yourBytes = bos1.toByteArray();
    System.out.println("1 Boolean length: " + yourBytes.length);
    }

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutput out = new ObjectOutputStream(bos);
    for (int i = 0; i < 1000; ++i) {
        out.writeObject(new Boolean(true)); // 47 bytes
    }
    byte[] yourBytes = bos.toByteArray();
    System.out.println("1000 Booleans length: " + yourBytes.length); // 7040 bytes

    final int count = 1000;

    ArrayList<Boolean> listBoolean = new ArrayList<>(count);
    listBoolean.addAll(Collections.nCopies(count, Boolean.TRUE));
    System.out.printf("ArrayList: %d%n", sizeOf(listBoolean)); // 5096 bytes

    Boolean[] arrayBoolean = new Boolean[count];
    Arrays.fill(arrayBoolean, true);
    System.out.printf("Boolean[]: %d%n", sizeOf(arrayBoolean)); // 5083 bytes

    boolean[] array = new boolean[count];
    Arrays.fill(array, true);
    System.out.printf("boolean[]: %d%n", sizeOf(array)); // 1027 bytes

    BitSet bits = new BitSet(count);
    bits.set(0, count);
    System.out.printf("BitSet: %d%n", sizeOf(bits)); // 201 bytes
}

static int sizeOf(Serializable obj) throws IOException {
    ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
    ObjectOutputStream objsOut = new ObjectOutputStream(bytesOut);
    objsOut.writeObject(obj);
    return bytesOut.toByteArray().length;
}

输出:

1 个布尔值长度:47(每个布尔值 47 个字节)

1000 个布尔值长度:7040(每个布尔值 7 个字节)

ArrayList:5096(每个布尔值 5 个字节)

Boolean[]: 5083(每个布尔值 5 个字节)

boolean[]: 1027(每个布尔值 1 个字节)

BitSet:201(每个布尔值 1 字节的 1/5)

【问题讨论】:

  • Java 序列化有一个非常特殊的目的。它不等同于 JSON 或 Protocol Buffers,对于只需要类似内容的用例来说,它可能有点过分了。

标签: java serialization


【解决方案1】:

虽然 Radiodef 已经阐明了为什么序列化对象的大小很大,但我想在这里再强调一点,这样我们就不会忘记底层 java 的序列化算法(几乎所有算法中)中存在的优化。

当您编写另一个 Integer 对象(或任何已写入的对象)时,在这种情况下您不会看到类似的大小(我的意思是大小不会是 81 * 2 = 162 字节),

ObjectOutput out = new ObjectOutputStream(bos);   
out.writeObject(new Integer(32));
out.writeObject(new Integer(65));
byte[] yourBytes = bos.toByteArray();
System.out.println("length: " + yourBytes.length + " bytes");

它的工作方式是,当第一次请求类的实例(对象)进行序列化时,它会写入关于整个类的信息。即包括类名,它写入类中存在的每个字段的名称。这就是字节数更多的原因。这基本上是为了正确处理类评估案例。

当它第一次发送类的元数据时,它还将相同的信息缓存到称为值缓存或间接表的本地缓存中。因此,下次当请求同一类的另一个实例进行序列化时(请记住,缓存仅适用于流级别,或在调用 reset() 之前),它只写入一个标记(仅 4 个字节的信息),以便大小会更少。

【讨论】:

  • 我明白了。 1 个整数是 81 个字节,而 2 个整数是 91 个字节,所以第二个只有 10 个字节。 10 字节是可接受的大小,因为我通常批量序列化,所以我会记住这一点。非常感谢。
  • 酷!不客气,如果您想要真正快速的序列化程序,您可以寻找性能比 java 序列化更好的 Kryo,但不能处理 java 序列化处理的所有场景。但你可以解决它。
【解决方案2】:

java.lang.Bytejava.lang.Integer 是对象,因此至少还需要存储它们的类的限定名称,以便它们被反序列化。还需要存储serialVersionUID 等。我们可以很容易地看到这些额外的信息是如何快速增加大小的。

如果你想了解序列化格式,JavaWorld上有一篇文章:http://www.javaworld.com/article/2072752/the-java-serialization-algorithm-revealed.html


如果您担心序列化数据的大小,请选择更紧凑的格式:

import java.util.*;
import java.io.*;

class Example {
    public static void main(String[] args) throws IOException {
        final int count = 1000;

        ArrayList<Boolean> list = new ArrayList<>(count);
        list.addAll(Collections.nCopies(count, Boolean.TRUE));
        System.out.printf("ArrayList: %d%n", sizeOf(list));

        boolean[] array = new boolean[count];
        Arrays.fill(array, true);
        System.out.printf("boolean[]: %d%n", sizeOf(array));

        BitSet bits = new BitSet(count);
        bits.set(0, count);
        System.out.printf("BitSet: %d%n", sizeOf(bits));
    }

    static int sizeOf(Serializable obj) throws IOException {
        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
        ObjectOutputStream objsOut = new ObjectOutputStream(bytesOut);
        objsOut.writeObject(obj);
        return bytesOut.toByteArray().length;
    }
}
ArrayList: 5096
boolean[]: 1027
BitSet: 201

Ideone 上的示例。

【讨论】:

  • 它看起来效率低得可怕。 81 个字节来存储一个整数。我的意思是即使你取 4 个字节的整数并将 8 个字节的类 ID 与 8 个字节的 serialVersionUID 相加,也只能加起来 20 个字节。即使单词“Integer”被转换为字符串并添加到序列化中,也只会添加 7*2(假设 UTF8)或 14 个字节。
  • 硬盘真的非常大。
  • 谢谢 Radiodef 或在这里分享我的文章链接 :-)。我很高兴看到这一点。 +1。
猜你喜欢
  • 2013-12-03
  • 1970-01-01
  • 1970-01-01
  • 2013-01-01
  • 2012-02-12
  • 2019-12-14
  • 1970-01-01
  • 2021-12-10
  • 2018-08-20
相关资源
最近更新 更多