在Scala/Java中有没有类似Python Pickle的简单无麻烦的零样板序列化?
有没有一种简单方便的方法可以在Scala或Java中进行序列化,类似于Python的pickle?pickle是一个非常简单的解决方案,效率还不错(也不是特别差),但它不考虑跨语言的兼容性、版本控制等问题,并且允许一些自定义。
我了解到的情况有:
- Java自带的序列化速度非常慢,大家都知道这一点([1], [2]),而且占用空间大,容易出问题。而且你还得把类标记为Serializable——这很烦,因为有些明显可以序列化的东西却没有这个标记(比如,很多Point2D的作者并没有把这些标记为Serializable)。
- Scala的BytePickle需要为每种你想序列化的类型写很多重复的代码,即使这样,它也无法处理(循环)对象图。
- jserial:这个库已经不再维护,而且似乎速度和占用空间都没有比默认的Java序列化快多少。
- kryo:不能序列化没有无参构造函数的对象,这是一个很大的限制。(而且你必须提前注册每个你打算序列化的类,否则会导致显著的速度下降和占用空间增加,不过即便如此,它的速度还是比pickle快。)
- protostuff:据我了解,你必须提前在一个“模式”中注册每个你打算序列化的类。
Kryo和protostuff是我找到的最接近的解决方案,但我想知道是否还有其他的选择(或者有没有什么使用这些库的方法是我应该知道的)。请提供一些使用示例!最好也能包括一些性能对比。
5 个回答
Twitter的chill库真是太棒了。它使用Kryo来进行数据的序列化,但使用起来非常简单。另外,它还提供了一种叫做MeatLocker[X]的类型,可以让任何类型X都变得可以序列化。
我觉得你最好使用kryo(我不知道还有其他不需要定义太多结构的替代方案,除了非二进制协议)。你提到pickle在没有注册类的情况下不会像kryo那样变慢或变得臃肿,但其实kryo即使不注册类,速度还是比pickle快,而且占用空间也更小。下面是一个简单的对比测试(当然,这只是我能轻松做的,结果仅供参考):
Python的pickle
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的热点JVM在64位Linux上)。如果我注册类,输出会是51字节,耗时18到25毫秒。
对比
不注册类时,kryo的空间占用大约是Python pickle的40%,耗时大约是3%。注册类后,kryo的空间占用大约是30%,耗时大约是2%。而且你总是可以写一个自定义的序列化器,以便获得更多的控制权。
编辑于2020年2月19日:请注意,正如下面的@federico提到的,这个回答已经不再有效,因为这个仓库已经被所有者归档了。
现在,Scala有了Scala-pickling,在某些情况下,它的表现和Kyro一样好,甚至更好——可以查看这个演示文稿的第34到39页。