为什么Java连载占用这么多space?
Why does Java serialization take up so much space?
我尝试序列化 Byte 和 Integer 的实例,当它们在另一端接收时,它们占用了多少 space 令我震惊。为什么一个 Integer 只需要 4 个字节,但在序列化时却占用了 10 倍以上的字节?我的意思是在 C++ 中,final class 有一个 64 位的 class 标识符,加上它的内容。离开这个逻辑,我希望一个整数在序列化时占用 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 个字节)
布尔值[]:5083(每个布尔值 5 个字节)
布尔值[]:1027(每个布尔值 1 个字节)
BitSet:201(每个布尔值 1 个字节的 1/5)
java.lang.Byte
和 java.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。
虽然 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");
它的工作方式是,当class的一个实例(对象)第一次被请求序列化时,它写入整个class的信息.即包括 class 名称,它会写入 class 中存在的每个字段的名称。这就是字节数更多的原因。这基本上是为了正确处理 class 评估案例。
当它第一次发送 class 的元数据时,它还会将相同的信息缓存到称为值缓存或间接 table 的本地缓存中。所以下次当另一个相同 class 的实例被请求序列化时(记住缓存只适用于流级别,或者在 reset() 被调用之前),它只写一个标记(只有 4 个字节的信息)这样尺寸会小一些。
我尝试序列化 Byte 和 Integer 的实例,当它们在另一端接收时,它们占用了多少 space 令我震惊。为什么一个 Integer 只需要 4 个字节,但在序列化时却占用了 10 倍以上的字节?我的意思是在 C++ 中,final class 有一个 64 位的 class 标识符,加上它的内容。离开这个逻辑,我希望一个整数在序列化时占用 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 个字节)
布尔值[]:5083(每个布尔值 5 个字节)
布尔值[]:1027(每个布尔值 1 个字节)
BitSet:201(每个布尔值 1 个字节的 1/5)
java.lang.Byte
和 java.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。
虽然 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");
它的工作方式是,当class的一个实例(对象)第一次被请求序列化时,它写入整个class的信息.即包括 class 名称,它会写入 class 中存在的每个字段的名称。这就是字节数更多的原因。这基本上是为了正确处理 class 评估案例。
当它第一次发送 class 的元数据时,它还会将相同的信息缓存到称为值缓存或间接 table 的本地缓存中。所以下次当另一个相同 class 的实例被请求序列化时(记住缓存只适用于流级别,或者在 reset() 被调用之前),它只写一个标记(只有 4 个字节的信息)这样尺寸会小一些。