使用Clojure数据结构与MapDB

3
我尝试直接使用Clojure的hashmap与MapDB配合使用,但出现了奇怪的行为。我查看了Clojure和MapDB的源代码,但无法理解问题所在。
首先一切看起来都很正常:
lein try org.mapdb/mapdb "1.0.6"

; defining a db for the first time
(import [org.mapdb DB DBMaker])
(defonce db (-> (DBMaker/newFileDB (java.io.File. "/tmp/mapdb"))
                .closeOnJvmShutdown
                .compressionEnable
                .make))
(defonce fruits (.getTreeMap db "fruits-store"))
(do (.put fruits :banana {:qty 2}) (.commit db))

(get fruits :banana)
=> {:qty 2}
(:qty (get fruits :banana))
=> 2
(first (keys (get fruits :banana)))
=> :qty
(= :qty (first (keys (get fruits :banana))))
=> true

CTRL-D
=> Bye for now!

然后我再次尝试访问数据:

lein try org.mapdb/mapdb "1.0.6"

; loading previsously created db
(import [org.mapdb DB DBMaker])
(defonce db (-> (DBMaker/newFileDB (java.io.File. "/tmp/mapdb"))
                .closeOnJvmShutdown
                .compressionEnable
                .make))
(defonce fruits (.getTreeMap db "fruits-store"))

(get fruits :banana)
=> {:qty 2}
(:qty (get fruits :banana))
=> nil
(first (keys (get fruits :banana)))
=> :qty
(= :qty (first (keys (get fruits :banana))))
=> false
(class (first (keys (get fruits :banana))))
=> clojure.lang.Keyword

同一个关键字为什么在使用 = 运算符时会产生不同的结果? 是否存在一些奇怪的引用问题?

看起来mapdb出了问题。 (def k? (first (keys (get fruits :banana)))) (= k? (keyword (name k?))) => false - noisesmith
再仔细看一下 - mapdb并不是专门支持Clojure数据类型的。我一点也不惊讶本地通用的Java序列化工具不能很好地处理Clojure关键字。我认为这类似于序列化布尔值和false无法正常工作的经典问题。你可能想要使用clj-mapdb(尽管它仍处于beta版,API在变动中)。 - noisesmith
1个回答

4
问题是由于关键字的相等性处理方式导致的。查看=函数的实现,我们可以看到,由于关键字不是clojure.lang.Numberclojure.lang.IPersistentCollection,因此它们的相等性是根据Object.equals方法来确定的。浏览clojure.lang.Keyword的源代码,我们了解到关键字没有覆盖Object.equals,因此当且仅当它们是同一个对象时,两个关键字才相等。
MapDB的默认序列化程序是org.mapdb.SerializerPojo,它是org.mapdb.SerializerBase的子类。在文档中,我们可以读到它是一个

使用“头字节”对大多数来自“java.lang”和“java.util”包的类进行序列化/反序列化的序列化器。

不幸的是,它与clojure.lang类的兼容性不是很好。它不能保留关键字的标识,从而破坏了相等性。为了解决这个问题,让我们尝试编写自己的序列化器,使用EDN格式——或者你可以考虑使用Nippy——并将其用于我们的MapDB。
(require '[clojure.edn :as edn])

(deftype EDNSeralizer []
  ;; See docs of org.mapdb.Serializer for semantics.
  org.mapdb.Serializer
  (fixedSize [_]
    -1)
  (serialize [_ out obj]
    (.writeUTF out (pr-str obj)))
  (deserialize [_ in available]
    (edn/read-string (.readUTF in)))
  ;; MapDB expects serializers to be serializable.
  java.io.Serializable)

(def edn-serializer (EDNSeralizer.))

(import [org.mapdb DB DBMaker])
(def db (.. (DBMaker/newFileDB (java.io.File. "/tmp/mapdb"))
            closeOnJvmShutdown
            compressionEnable
            make))

(def more-fruits (.. db
                     (createTreeMap "more-fruits")
                     (valueSerializer (EDNSeralizer.))
                     (makeOrGet)))
(.put more-fruits :banana {:qty 2})
(.commit db)

如果在定义了EDNSeralizer的JVM中重新打开more-fruits tree map,则存储在其中的:qty对象将与任何其他:qty实例相同。 因此,等式检查将正常工作。


感谢提供源代码参考和解释。有趣的是,clojure.lang.Keyword 实现了 Comparable 接口,这使得以下代码正确: (.compareTo :qty (first (keys (get fruits :banana)))) - lerouxrgd
我不清楚edn-serializer变量在MapDB API中的使用方式。你能详细解释一下吗? - mlb

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