Clojure数据结构序列化

17

我有一个复杂的Clojure数据结构需要序列化——基本上是我正在开发的在线游戏的整个当前游戏状态,以便我可以实现保存游戏文件。

我的要求如下:

  • 某种人类可读的文本格式(我可能更喜欢s-expressions、JSON和XML,但也可以考虑其他格式)
  • 支持所有通常的Clojure数据结构、关键字和基元
  • 能够为自定义Java类、defrecords等提供自定义序列化/反序列化函数(这很重要,因为我需要在几种情况下执行类似于Java的readResolve操作)
  • 优秀的性能是一个很不错的附加要求

有什么好的建议吗?


不了解Clojure,是否有任何原因导致无法使用从Clojure调用的标准Java序列化机制来实现此目标? - Gian
@Gian - 是的,这当然是可能的,但我正在尝试学习“Clojure方式”来做事情 :-) - mikera
在我看来,Clojure 的方式是在 Java 提供的解决问题的良好方案中使用它们。 :-) Serializable 可能是短期存储/传输数据结构的好方法。话虽如此,我想对于这种用例,需要更适合长期存储的格式,这可能由 print-dup 提供。(如果实现核心 Clojure 数据结构的类的结构发生变化,Serializable 可能会遇到问题;而 print-dup 不太可能。) - Michał Marczyk
4个回答

11

如果您想将事物序列化为S表达式,您可以使用print-dup

(binding [*print-dup* true] (println [1 2 3]))
; prints [1 2 3]

(defrecord Foo [x])
; => user.Foo
(binding [*print-dup* true] (println (Foo. :foo)))
; prints #=(user.Foo/create {:x :foo})
请注意,打印一个包含10个对同一向量的引用的结构体,并读回它,会得到一个由10个单独(而不是相同)的向量组成的数据结构,尽管这些向量在结构上是等价的。
要在没有提供默认实现的情况下使用它,请实现多方法 clojure.core/print-dup
另外,Clojure 1.2 中的许多内容都是 java.io.Serializable 的。
(every? (partial instance? java.io.Serializable)
        [{1 2} #{"asdf"} :foo 'foo (fn [] :foo)])
; => true

(defrecord Foo [])
(instance? java.io.Serializable (Foo.))
; => true
注意,应避免序列化运行时创建的fn,它们是具有奇怪名称的一次性类的实例,并且在重新启动JVM后无法反序列化。使用AOT编译,fn将获得自己的固定类名。
更新:正如在问题的评论中提到的那样,Serializable最适用于短期存储/数据传输,而print-dup应更加强大作为长期存储解决方案(可在应用程序的许多版本、Clojure等之间使用)。原因是print-dup不以任何方式依赖于被序列化的类的结构(因此,今天打印成print-dup的向量在向量实现从Java切换到Clojure的deftype时仍然可读取)。

我认为print-dup可能是这种情况下更优秀的解决方案,可以看一下我在问题上的评论...如果存档变得很大,打印输出总是可以被压缩的。 - Michał Marczyk

7

edn-format 现已成为使用Clojure数据结构传输数据的标准。

它非常适合序列化Clojure数据结构/值,并且被多种语言支持,因此也可以用作数据交换格式。


这对于核心的Clojure数据结构非常有效,但是如果您在其中有更奇特的数据结构,比如PriorityMap(它看起来就像PersistentMap但行为不同),那么您需要使用读取器宏或其他东西,对吧? - Hendekagon
1
他们在edn格式中考虑到了这一点:您可以“标记”值以指示它们属于特殊类型,并编写自己的处理程序来构造适当的类。这非常聪明! - mikera

5

如果一切都是Clojure数据结构,那么它已经被序列化了(因为代码<->数据)。只需将数据结构转储到磁盘上即可。要恢复,请加载它们并执行(eval)。


1
*out* 绑定到文件流并使用 (pr)(然后使用 (load-file) 读取它)。请参见 http://groups.google.com/group/clojure/browse_thread/thread/cb5246d07142a3dc?fwc=2&pli=1 获取一个可行的示例。 - G__
2
评估它们不危险吗?有人可能会在其中潜入一些代码。我认为应该阅读但不评估它们。 - pupeno
这是任何序列化都存在的问题。如果不进行评估,那么就必须添加整个读取/解析层。如果这是一个不受信任的用户可以访问文件的情况,那么我认为最好扩展一下并加入加密层或哈希。 - G__

3

对于 JSON,您可以使用标准的 clojure-contrib.json。尽管我记得所有 Clojure 对象都应该是可序列化的...


尝试过了,它不喜欢我的Java类: 它会抛出例如java.lang.Exception: 不知道如何编写mikera.persistent.SparseMap类的JSON。有没有办法为自己的类提供自定义序列化函数呢? - mikera
你可以让你的类实现 clojure.contrib.json.Write_JSON 接口(或者如果该类是 Clojure 记录/类型,则使用协议 clojure.contrib.json/Write-JSON)。不过,我不确定如何读取它们;对于非标准数据结构,你可能需要使用像 YAML 这样的东西。快速的谷歌搜索发现 clj-yaml http://github.com/lancepantz/clj-yaml 可能是一个解决方案,但我并不了解该项目。 - Michał Marczyk
啊,刚注意到 clj-yaml 的 README 上说它目前只支持反序列化... - Michał Marczyk

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