自制序列化 vs Java 序列化

15

我有一个POJO对象需要持久化到数据库,当前的设计将其字段指定为单个字符串列,并且添加其他字段到表中不是一个选项。

也就是说,这些对象需要以某种方式进行序列化。所以,为了基本实现,我设计了自己的对象序列化形式,这意味着将所有字段连接成一个美好的字符串,由我选择的分隔符分隔。但这样做相当丑陋,会导致问题,例如如果其中一个字段包含我的分隔符。

因此,我尝试了基本的Java序列化,但从我进行的基本测试中可以看出,这变成了一项非常昂贵的操作(构建ByteArrayOutputStream,ObjectOutputStream等,反序列化同理)。

那么我的选择是什么?在数据库中序列化对象的首选方法是什么?

编辑:这将是我的项目中非常普遍的操作,因此必须将开销保持到最小,并且性能非常重要。另外,第三方解决方案很好,但无关紧要(通常会产生我试图避免的开销)。

14个回答

12

Elliot Rusty Harold撰写了一篇有关在他的XOM库中使用Java对象序列化的有力论据。同样的原则也适用于你。内置的Java序列化是针对Java特定的,易碎的和缓慢的,因此最好避免使用。

你大致上使用了基于字符串的格式的正确思路。正如你所述,问题在于你遇到了分隔符方面的格式/语法问题。解决方法是使用已经建立处理此类问题的格式。如果这是一种标准化的格式,则您还可以使用其他库/语言来操作它。此外,基于字符串的格式意味着您有希望通过查看数据来理解它。二进制格式消除了这个选项。

XML和JSON是两个很好的选择;它们是标准化的,基于文本的,灵活的,可读的,并且具有许多库支持。它们的性能也会出乎意料地好(有时甚至比Java序列化更快)。


3
我发现XML和JSON比Java序列化慢大约5倍。你有任何例子可以证明它们更快吗? - Peter Lawrey
1
内置的Java序列化也是特定于JVM的。在任何实际情况下都不具备可移植性。 - mcjabberz
那个链接中没有任何构成“好的论点”的东西,甚至不是一个论点。只是一系列随意的评论,其中并非所有都正确。 - user207421

12
你可以尝试使用Google的开源项目Protocol Buffers,据说它比XML生成更短的序列化格式,并且运行速度更快。此外,它也能轻松处理新增字段(插入默认值)。

4
您需要在解决方案中考虑版本控制。任何涉及二进制对象序列化的解决方案都可能遇到数据不兼容的问题。如何将旧版本数据加载到新版本的对象中?
因此,上面提到的涉及序列化为名称/值对的解决方案可能是您想使用的方法。
一种解决方案是将版本号作为一个字段值之一。随着新字段的添加、修改或删除,版本号可以进行修改。
在反序列化数据时,您可以为每个版本使用不同的反序列化处理程序,以将数据从一个版本转换为另一个版本。

3
考虑将数据放入一个Properties对象中,并使用它的load()/store()序列化。这是一种基于文本的技术,因此在数据库中仍然可读:
public String getFieldsAsString() {
  Properties data = new Properties();
  data.setProperty( "foo", this.getFoo() );
  data.setProperty( "bar", this.getBar() );
  ...
  ByteArrayOutputStream out = new ByteArrayOutputStream();
  data.store( out, "" );
  return new String( out.toByteArray(), "8859-1" );   //store() always uses this encoding
}

要从字符串中加载,请使用新的Properties对象并使用load()来加载数据。
与Java序列化相比,这种方法更易读和紧凑。
如果您需要支持不同的数据类型(即不仅仅是字符串),请使用BeanUtils将每个字段转换为字符串表示形式,并从该字符串表示形式中进行转换。

Jason,感谢你的帮助,但是最后两个解决方案不适用于此问题。你能详细说明第一个解决方案吗? - Yuval Adam

3

XStreamYAMLOGNL是常用的易于序列化的技术。XML一直以来都是最常用的,但是OGNL提供了最少的元数据和最大的灵活性。


2
JavaBeans标准持久化机制怎么样?
java.beans.XMLEncoder
java.beans.XMLDecoder

这些工具能够从已持久化为XML的文件中创建Java POJOs。我记得它看起来有点像...

<object class="java.util.HashMap">
    <void method="put">
        <string>Hello</string>
        <float>1</float>
    </void>
</object>

您需要提供PersistenceDelegate类,以便它知道如何持久化用户定义的类。假设您不删除任何公共方法,它对模式更改是有弹性的。


2
我认为,如果你的POJO包含字符串和基本类型,那么你的初始方法并不算太糟糕。你可以强制转义分隔符以防止损坏。此外,如果你使用Hibernate,你可以将序列化封装在自定义类型中。
如果你不介意引入另一个依赖项,Hessian被认为是一种更有效的Java对象序列化方式。

1

您可以通过将对象外部化来优化序列化。 这将使您完全控制如何序列化并提高过程的性能。 只要您的POJO很简单(即不引用其他对象),这就很容易做到,否则您可能会轻松破坏序列化。

此处有教程

编辑:并不意味着这是首选方法,但如果性能至关重要且只能在表中使用字符串列,则您的选择非常有限。


1

如果您正在使用分隔符,可以使用一些特殊字符来代替,例如 \0,或者一些特殊的符号http://unicode.org/charts/symbols.html

然而,将数据发送到数据库并进行持久化所花费的时间很可能比序列化的成本更高。因此,我建议从简单易读的东西(如 XStream)开始,并查看应用程序花费大部分时间的地方,并对其进行优化。


0
我有一个特定的POJO需要在数据库上持久化,当前设计将其字段指定为单个字符串列,并在表中添加其他字段不是一个选项。
你能创建一个新的表并在该列中放置一个外键吗?!? :) 我怀疑不能,但让我们涵盖所有基础知识吧!
序列化: 我们最近进行了这个讨论,以便如果我们的应用程序崩溃,我们可以在先前的状态下重新启动它。 我们基本上将一个持久性事件分派到队列中,然后这个事件获取对象,锁定它,然后对其进行序列化。 这似乎非常快。 您要序列化多少数据? 您可以使任何变量中断(即缓存变量)吗? 您可以考虑拆分您的序列化吗? 注意:如果您的对象更改(锁定)或类更改(不同的序列化ID),会发生什么? 您需要升级所有已序列化为最新类的内容。 或许您只需要在过夜时存储它,那就无所谓了?
XML: 您可以使用类似xstream的东西来实现这一点。 构建自定义内容是可行的(一个不错的面试问题!),但我可能不会自己这样做。 为什么麻烦呢? 请记住,如果您具有循环链接或多次引用对象的引用。 重建对象并不是那么轻松。

数据库存储: 如果您使用的是Oracle 10g来存储BLOB,则升级到最新版本,因为c/blob性能大幅提高。如果我们谈论大量数据,则可以将输出流压缩?

这是一个实时应用程序,还是会有1-2秒的暂停,您可以安全地持久化实际对象吗?如果您有时间,那么您可以克隆它,然后在另一个线程上持久化克隆。持久化是用于什么的?它是否关键需要在事务内完成?


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