Scala/Java 中的简单、无忧、零样板序列化,类似于 Python 的 Pickle?

Posted

技术标签:

【中文标题】Scala/Java 中的简单、无忧、零样板序列化,类似于 Python 的 Pickle?【英文标题】:Simple, hassle-free, zero-boilerplate serialization in Scala/Java similar to Python's Pickle? 【发布时间】:2011-11-27 06:44:09 【问题描述】:

在 Scala/Java 中是否有一种类似于 Python 的 pickle 的简单、无忧的序列化方法? Pickle 是一个非常简单的解决方案,在空间和时间上相当高效(即不是很糟糕),但不关心跨语言可访问性、版本控制等,并且允许可选的自定义。

我知道的:

Java 的内置序列化非常缓慢([1]、[2])、臃肿且脆弱。还必须将类标记为可序列化——当有明显可序列化但没有该注释的事物时很烦人(例如,没有多少 Point2D 作者将这些标记为可序列化)。 Scala 的 BytePickle 需要一堆样板文件来处理您想要腌制的每种类型,即便如此 doesn't work with (cyclic) object graphs。 jserial:未维护且 doesn't seem to be that much faster/smaller 比默认的 Java 序列化。 kryo: Cannot (de-)serialize objects with no 0-arg ctors,这是一个严重的限制。 (另外你必须注册你计划序列化的每个类,否则你会得到significant slowdowns/bloat,但即便如此它仍然比pickle更快。) protostuff:AFAICT,您必须在“模式”中预先注册您打算序列化的每个类。

Kryo 和 protostuff 是我找到的最接近的解决方案,但我想知道是否还有其他解决方案(或者是否有一些我应该注意的使用方法)。请包括使用示例!理想情况下还包括基准。

【问题讨论】:

我觉得这样比较有点不公平,因为 Python 自然比 Java 慢很多。 Python 中的“快速”序列化器可能比 Java 中的“慢”序列化器慢得多。 @NullUserExceptionఠ_ఠ 你说得对,如果能有一些方法来比较 Python pickle 和 Java 序列化,那就太好了。也就是说,Pickle(Python 2.x 中的 cPickle)是 C 语言,而不是 Python。 根据我的经验,我相信对于可比较的任务,Java 序列化比 pickle 慢得多(跨语言比较事物总是很棘手)。我肯定知道对于类似的任务,它比泡菜更臃肿。也许有人可以提供数字(或者也许我最终会找到时间这样做)?另外,我认为 Pickle 和 Java 序列化之间同样重要的一点是,您不必依赖标记为 Serializable 的所有内容。 对于 kryo,还有一个插件项目 github.com/magro/kryo-serializers,如果您使用的是 sun/oracle jvm,它允许(反)序列化没有 0-arg 构造函数的对象。 在 Kryo 2.x 中,使用kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()) 获得基于反射的构造函数实例化,没有任何 0-arg 构造函数。 【参考方案1】:

实际上,我认为您最好使用 kryo(我不知道除了非二进制协议之外提供较少模式定义的替代方案)。您提到 pickle 不会受到 kryo 没有注册课程的减速和膨胀的影响,但是即使没有注册课程,kryo 仍然比 pickle 更快,更不臃肿。请参阅以下微基准(显然,它是带着一粒盐,但这是我可以轻松做到的):

Python 泡菜

import pickle
import time
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
people = [Person("Alex", 20), Person("Barbara", 25), Person("Charles", 30), Person("David", 35), Person("Emily", 40)]
for i in xrange(10000):
    output = pickle.dumps(people, -1)
    if i == 0: print len(output)
start_time = time.time()
for i in xrange(10000):
    output = pickle.dumps(people, -1)
print time.time() - start_time    

为我输出 174 字节和 1.18-1.23 秒(64 位 Linux 上的 Python 2.7.1)

Scala kryo

import com.esotericsoftware.kryo._
import java.io._
class Person(val name: String, val age: Int)
object MyApp extends App 
  val people = Array(new Person("Alex", 20), new Person("Barbara", 25), new Person("Charles", 30), new Person("David", 35), new Person("Emily", 40))
  val kryo = new Kryo
  kryo.setRegistrationOptional(true)
  val buffer = new ObjectBuffer(kryo)
  for (i <- 0 until 10000) 
    val output = new ByteArrayOutputStream
    buffer.writeObject(output, people)
    if (i == 0) println(output.size)
  
  val startTime = System.nanoTime
  for (i <- 0 until 10000) 
    val output = new ByteArrayOutputStream
    buffer.writeObject(output, people)
  
  println((System.nanoTime - startTime) / 1e9)

为我输出 68 字节和 30-40 毫秒(Kryo 1.04、Scala 2.9.1、Java 1.6.0.26 在 64 位 Linux 上的热点 JVM)。作为比较,如果我注册类,它会输出 51 个字节和 18-25ms。

比较

Kryo 在不注册类时使用大约 40% 的空间和 3% 的时间作为 Python pickle,在注册类时使用大约 30% 的空间和 2% 的时间。当您需要更多控制权时,您始终可以编写自定义序列化程序。

【讨论】:

感谢您提供的数字。不过,我似乎无法反序列化这些对象,这是一个严重的限制(用这个更新了我的问题)。我得到由“com.esotericsoftware.kryo.SerializationException:无法创建类(缺少无参数构造函数):Person”引起的“com.esotericsoftware.kryo.SerializationException:无法反序列化类型的对象:Person”。 如果您使用的是 sun/oracle jvm,您可以使用 github.com/magro/kryo-serializers 来反序列化没有 0-arg 构造函数的对象:只需将“new Kryo”更改为“new KryoReflectionFactorySupport”。 @MartinGrotzke:今天你让我成为了一个非常快乐的人。令人印象深刻的工作。我看到自己已经在你的图书馆里拉皮条了。我真的希望它能够成为 kryo 本身。 @杨酷!期待您的拉取请求 :-) 在 Kryo 2.x 中,使用 kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()) 获得基于反射的构造函数实例化,无需任何 0-arg 构造函数。【参考方案2】:

编辑 2020-02-19:请注意,正如下面@federico 所述,此答案不再有效,因为存储库已被所有者存档。

Scala 现在有 Scala-pickling,其性能与 Kyro 一样好或更好,具体取决于场景 - 请参阅 this 演示文稿中的幻灯片 34-39。

【讨论】:

我认为创建完整的问题不值得,所以我在这里问:目前是否可以在 android 上使用这个库?如果是,那么我应该使用什么工具链?是否可以在 Eclipse + ADT + github.com/banshee/AndroidProguardScala 中使用,或者我需要更聪明的东西(sbt,maven,...)? 我没有尝试在 Android 上使用它,所以我不知道。如果您发现任何问题,您可以尝试在 *** 上发布问题 :) "这个仓库已经被所有者归档。它现在是只读的。"【参考方案3】:

Twitter 的chill library 太棒了。它使用 Kryo 进行序列化,但使用起来非常简单。也不错:提供了一个 MeatLocker[X] 类型,它使任何 X 都可以序列化。

【讨论】:

【参考方案4】:

我会推荐SBinary。它使用在编译时解析的隐式,因此它非常有效且类型安全。它内置了对许多常见 Scala 数据类型的支持。您必须手动为您的(案例)类编写序列化代码,但这很容易做到。

A usage example for a simple ADT

【讨论】:

这里的第 18 到 40 行发生了什么? github.com/harrah/sbinary/blob/master/core/src/… 我见过 SBinary。关键是您必须编写自己的序列化代码。这可能是我列出的所有选项中最冗长的,这就是为什么我决定它没有进入我的列表。 @KnutArneVedaa 作者使用预处理器 (FMPP) 生成元组格式。请参阅 sbt 项目定义。 这家伙说声明一个空的构造函数对他来说很重要,您建议使用自定义序列化程序作为解决方案?【参考方案5】:

另一个不错的选择是最近的 (2016) **netvl/picopickle**:

,几乎没有依赖(核心库仅依赖于shapeless)。 可扩展性:您可以为您的类型定义自己的序列化器,也可以创建自定义后端,也就是说,您可以为不同的序列化格式(集合、JSON、BSON 等)使用相同的库;还可以自定义序列化行为的其他部分,例如 null 处理。 灵活性和方便:默认的序列化格式适用于大多数用途,但在方便的转换器 DSL 的支持下,它几乎可以任意定制。 无反射的静态序列化:无形的泛型宏用于为任意类型提供序列化器,这意味着不使用反射。

例如:

基于 Jawn 的pickler 还提供了额外的函数readString()/writeString()readAst()/writeAst(),它们分别将对象序列化为字符串和将JSON AST 序列化为字符串:

import io.github.netvl.picopickle.backends.jawn.JsonPickler._

case class A(x: Int, y: String)

writeString(A(10, "hi")) shouldEqual """"x":10,"y":"hi""""
readString[A](""""x":10,"y":"hi"""") shouldEqual A(10, "hi")

【讨论】:

以上是关于Scala/Java 中的简单、无忧、零样板序列化,类似于 Python 的 Pickle?的主要内容,如果未能解决你的问题,请参考以下文章

SYSTEM32 下的几乎所有文件的简单说明(原由无忧启动论坛老毛桃出)

python reportlab简单的样板

python reportlab简单的样板

python 用于命令行管理脚本的简单样板

python 用于命令行管理脚本的简单样板。

html html5样板 - 死简单