【问题标题】:Efficient binary serialization for Clojure/JavaClojure/Java 的高效二进制序列化
【发布时间】:2011-10-09 05:28:36
【问题描述】:

我正在寻找一种将 Clojure 对象有效地序列化为 二进制 格式的方法 - 即不仅仅是进行经典的打印和读取文本序列化。

即我想做类似的事情:

(def orig-data {:name "Data Object" 
                :data (get-big-java-array) 
                :other (get-clojure-data-stuff)})

(def binary (serialize orig-data))

;; here "binary" is a raw binary form, e.g. a Java byte array
;; so it can be persisted in key/value store or sent over network etc.

;; now check it works!

(def new-data (deserialize binary))

(= new-data orig-data)
=> true

动机是我有一些包含大量二进制数据(在 Java 数组中)的大型数据结构,我想避免将这些全部转换为文本并再次转换回来的开销。此外,我试图保持格式紧凑,以最大限度地减少网络带宽的使用。

我希望拥有的特定功能:

  • 轻量级的纯 Java 实现
  • 支持 Clojure 的所有标准数据结构以及所有 Java 原语、数组等。
  • 不需要额外的构建步骤/配置文件 - 我宁愿它“开箱即用”
  • 在所需的处理时间方面表现良好
  • 二进制编码表示的紧凑性

在 Clojure 中执行此操作的最佳/标准方法是什么?

【问题讨论】:

  • 根据您的编码内容和编码方式,文本可以比某些二进制格式更快、更紧凑。通常,序列化的开销是使用反射而不是转换,因此支持任意数据结构的需求很可能是您真正的问题。这比较了罐装与 Java 序列化,vanillajava.blogspot.com/2011/08/…
  • 它是否必须能够处理对运行时对象的引用,例如 atom 和 refs?
  • 我在使用 Kryo 与 Cascalog、ElephantDB 和 Storm 时取得了巨大的成功。 carbonite,由 revelytix 提供,为大多数 Clojure 数据结构提供开箱即用的序列化程序。

标签: java serialization clojure


【解决方案1】:

我可能在这里遗漏了一些东西,但标准 Java 序列化有什么问题?太慢了,太大了,还有别的吗?

用于普通 Java 序列化的 Clojure 包装器可能是这样的:

(defn serializable? [v]
  (instance? java.io.Serializable v))

(defn serialize 
  "Serializes value, returns a byte array"
  [v]
  (let [buff (java.io.ByteArrayOutputStream. 1024)]
    (with-open [dos (java.io.ObjectOutputStream. buff)]
      (.writeObject dos v))
    (.toByteArray buff)))

(defn deserialize 
  "Accepts a byte array, returns deserialized value"
  [bytes]
  (with-open [dis (java.io.ObjectInputStream.
                   (java.io.ByteArrayInputStream. bytes))]
    (.readObject dis)))

 user> (= (range 10) (deserialize (serialize (range 10))))
 true

有些值不能被序列化,例如Java 流和 Clojure atom/agent/future,但它应该适用于大多数普通值,包括 Java 原语和数组以及 Clojure 函数、集合和记录。

你是否真的保存任何东西取决于。在我对小型数据集的有限测试中,序列化为文本和二进制的时间和空间似乎大致相同。

但是对于大量数据是 Java 基元数组的特殊情况,Java 序列化可以更快几个数量级并节省大量空间。 (在笔记本电脑上快速测试,100k 随机字节:序列化 0.9 ms,100kB;文本 490 ms,700kB。)

请注意,(= new-data orig-data) 测试不适用于数组(它委托给 Java 的 equals,它对于数组只是测试它是否是同一个对象),因此您可能想要/需要编写自己的相等函数来测试序列化。

user> (def a (range 10))
user> (= a (range 10))
true
user> (= (into-array a) (into-array a))
false
user> (.equals (into-array a) (into-array a))
false
user> (java.util.Arrays/equals (into-array a) (into-array a))
true

【讨论】:

  • 很有趣 - 我以前在 Java 序列化方面有过糟糕的经历(太慢、消息体积过大),但从您的测试看来,它实际上可能对大型数组非常有效。
  • @mikera 我认为 Java 序列化的主要问题是意外的依赖关系,因此您最终可能会序列化一半的 JVM。但如果你坚持简单的价值观,那就很好了。 This blog 发现对于一个简单的 POJO,Java 序列化比 Google protobufs(稍微)更快和更小。
【解决方案2】:

Nippy 是恕我直言的最佳选择之一:https://github.com/ptaoussanis/nippy

【讨论】:

    【解决方案3】:

    您是否考虑过 Google 的 protobuf?您可能需要检查 GitHub repository 与 Clojure 的接口。

    【讨论】:

    • 有趣.... 很高兴看到有一个 Clojure 包装器!但是,除非我弄错了,否则这不允许序列化任意 Clojure 对象,即您必须在 .proto 文件中预先指定您的结构?
    • 很高兴您发现它有帮助!不幸的是,我没有遵循 arbitraty 的含义,但无论哪种方式,就我而言,.proto 文件正是您定义数据结构的地方(想想:模式),其他一切都由 API 为您完成。
    • 我的意思是:我希望能够序列化和反序列化 Clojure 结构 不必提前预定义数据结构。似乎这应该是可能的,因为所有 Clojure 数据结构都只是映射、集合、列表等,其中包含一些基本的 Java 对象。由于许多数据结构是在 Clojure 运行时动态创建的,因此几乎不可能在其中指定它们前进.......
    【解决方案4】:

    如果您事先没有架构,那么序列化为文本可能是您最好的选择。一般来说,要序列化任意数据,您需要做很多工作来保留对象图,并进行反射以查看如何序列化所有内容……至少 Clojure 的打印机可以对@987654321 进行静态、无反射查找@为每个项目。

    相反,如果您真的想要优化的线路格式,您需要定义一个模式。我使用了 java 中的 thrift 和 clojure 中的 protobuf:两者都不是很有趣,但如果您提前计划,它也不会非常繁重。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-04
      • 2010-10-17
      • 1970-01-01
      • 2011-01-14
      • 2010-10-01
      • 2016-07-27
      • 2011-09-22
      • 1970-01-01
      相关资源
      最近更新 更多