Java序列化效率如何?

3
我们有一个称为Row的类,它代表结果集中的一行。我们需要编写一个List<Row>到文件中,以便很久以后可以检索。

其中一种方法是使用Java的序列化支持。

我认为最好的方式是在Row类内部实现序列化。然后,我们将使用List<Row>serialize方法来写入文件。

我想知道,这样做的效率会有多高?相对于编写一个将我们的List<Row>对象转换为CSV文件的适配器,它是否会占用更多的空间?


2
@ElliotFrisch serialVersionUID 不会随每个对象一起传输。它是每个 newClassDesc 传输一次,而每个类每个流只传输一次 newClassDesc - user207421
@ktm5124 不,这永远不会编译。顺便说一下,序列化更多地是一种在不同机器之间通信对象的方式,而不是一种存储方法... 如果您需要长期存储此内容,则可能有更好的方法来完成此操作(其他格式、数据库等)。 - Dici
@ktm5124 不,因为没有这样的方法。你要找的是 ObjectOutputStream.writeObject(list) - user207421
1
@Dici 这是在文档中给一个方法命名的一种常见简写方式,不一定需要编译通过。Socket.close() 是另一个例子。 - user207421
@EJP 我知道这一点,但我从未见过泛型像这样使用,除非你有来自文档的具体示例向我展示。 - Dici
显示剩余7条评论
2个回答

4
它取决于行的类型,以及您正在保存的数据的大小和其他方面。一方面,Java序列化协议包括序列化中提到的每个类的元数据。这需要大量空间。另一方面:Java序列化仅在每次序列化时包含一次元数据。因此,如果您序列化了许多相同类的实例,则元数据成本变得微不足道。在CSV文件中,所有非文本数据都必须转换为文本。在某些情况下(例如大型数字、浮点数、布尔值),文本表示法将比Java序列化中使用的二进制表示法更大。但我认为您可能正在关注错误的事情:除非您生成的文件非常巨大,否则大小可能并不重要。磁盘空间很便宜。无论哪种情况,文件都可能是可以压缩的,密度更小的形式可能更易于压缩。更重要的是,表示是否适合用途,例如:您希望它易于阅读吗?您想让它被非Java程序(包括shell脚本)读取吗?您需要担心Java代码的更改会引入类与序列化版本问题吗?您想能够流式传输数据吗?(写或读)。

2
@Tom 实际上不是这样的。请查看此答案 https://dev59.com/KXRA5IYBdhLWcg3w9ivq#769675,同时也可以参考[Csv Schema](http://digital-preservation.github.io/csv-schema/csv-schema-1.0.html#basics)。 - Onur
1
@Tom - 良好格式的CSV通过引用来处理这种情况。然而,还有另一个问题,就是有许多CSV变体,您需要知道使用哪个才能正确读取它。 - Stephen C
2
@Tom - 因为这些文件是由那些不知道自己在做什么的人编写的应用程序所写入的 :-) 提示:不要手动编写CSV读取器/写入器。如果可以的话,请使用库。 - Stephen C
1
提示:@Tom - 你不是唯一一个阅读这些评论的人。 - Stephen C
1
@Tom并不意味着我只是在和你说话。它的用法并不是这样的。 - Stephen C
显示剩余4条评论

3

在某些情况下,Java序列化将比简单地写入CSV文件更加占用空间,因为它存储额外的元数据以识别类类型。

我通过两个简单的测试程序验证了这种情况。第一个程序将一个int数组写入.csv文件中。

import java.io.*;

public class CSVDemo {
  public static void main(String [] args) {
    try {
       PrintWriter pw = new PrintWriter(new File("dummy.csv"));
       StringBuilder sb = new StringBuilder();
       for(int i = 0; i < 1000; i++){
         sb.append(1);
         sb.append(",");
       }

       pw.write(sb.toString());
       pw.close();
       System.out.printf("Data is saved in dummy.csv");
    } catch(FileNotFoundException e) {
        e.printStackTrace();
    }
  }
}

第二个例子将一个包含整数数组的对象序列化到.ser文件中。
import java.io.*;

public class SerializeDemo
{
   public static void main(String [] args)
   {
      DummyData dummy = new DummyData();

      try {
         FileOutputStream fileOut = new FileOutputStream("dummy.ser");
         ObjectOutputStream out = new ObjectOutputStream(fileOut);
         out.writeObject(dummy);
         out.close();
         fileOut.close();
         System.out.printf("Serialized data is saved in dummy.ser");
      } catch(IOException i) {
          i.printStackTrace();
      }
   }

   public static class DummyData implements java.io.Serializable{
     int[] data = new int[1000];
     public DummyData(){
       for(int i = 0; i < 1000; i++){
         data[i] = 1;
       }
     }
   }
}

.ser文件占用了4079字节。.csv文件占用了2000字节。当然,这只是您使用情况的简化表述(我将int类型等同于您的Row类型),但总体趋势应该是相同的。

尝试使用更大的数字得到相同结果。使用100000个整数得到.ser文件约为400KB,.csv文件约为200KB。

不过,正如下面的评论指出的那样,如果选择随机整数值,.csv文件实际上会变得更大。


实际上,你的 CSV 文件存在一个轻微的错误。CSV 使用“,”来分隔列,而需要使用“\r\n”来分隔行。所以sb.append(","); 应该改为 sb.append("\r\n"); 这样结果是一个 3000 字节的文件而不是 2000 字节。 - Onur
2
请注意,此特定示例的属性使序列化更糟糕。如果您序列化使用“Random.nextInt()选择的大量(足够大)int`值的数组,则CSV形式将更大。 - Stephen C
1
证明 @StephenC 的评论【使用随机值进行测试】 - Onur
@StephenC 很有趣,我不知道。为什么呢?我已经修改了我的答案以反映您的评论。谢谢! - adao7000
@adao7000 我猜这是因为在序列化格式中,一个 int 总是占用 32 位,而存储为字符时,一个一位数的整数只需要 8 位。一旦你开始存储比 999 大的数字,CSV 格式就比二进制更浪费空间了。考虑到在 1000 和 2^32 - 1 之间的数字要比 0 到 999 之间的数字多得多,因此使用二进制格式来存储整数数组确实更好。 - Dici
顺便说一下,你可以很容易地验证我所说的:在CSV文件中,您存储了1000个逗号和1000个1,即2000个字符=> 2000个字节。在序列化形式中,您存储了1000个32位整数=> 4000个字节。我推测剩余的78个字节来自已序列化的类的某些元数据(可能是字符串“[package.name].SerializeDemo.DummyData”和其他一些内容)。 - Dici

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接