由于 JVM 的大小限制,在编译代码中嵌入这么大的数据可能是不可能的。特别是,任何一种方法的长度都不能超过 64 KiB。以我在下面进一步描述的方式嵌入数据也需要在它将要存在的类文件中包含大量内容;似乎不是一个好主意。
鉴于您使用的是只读数据结构,您可以构建它一次,然后将其发送到.clj
/ .edn
(用于edn,基于 Clojure 文字符号的序列化格式),然后将该文件包含在您的类路径中作为“资源”,以便它包含在 überjar 中(在resources/
默认 Leiningen 设置中;然后它将被包含在 überjar 中,除非被:uberjar-exclusions
in排除project.clj
)并在运行时以 Clojure 阅读器的全速从资源中读取它:
(ns foo.core
(:require [clojure.java.io :as io]))
(defn get-the-huge-data-structure []
(let [r (io/resource "huge.edn")
rdr (java.io.PushbackReader. (io/reader r))]
(read r)))
;; if you then do something like this:
(def ds (get-the-huge-data-structure))
;; your app will load the data as soon as this namespace is required;
;; for your :main namespace, this means as soon as the app starts;
;; note that if you use AOT compilation, it'll also be loaded at
;; compile time
您也可以不将其添加到 überjar,而是在运行您的应用程序时将其添加到类路径中。这样,您的 überjar 本身就不必很大。
可以使用print-method
(序列化时)和阅读器标签(反序列化时)来处理持久 Clojure 数据以外的东西。Arthur 已经演示了使用阅读器标签;使用print-method
,你会做类似的事情
(defmethod print-method clojure.lang.Ref [x writer]
(.write writer "#ref ")
(print-method @x writer))
;; from the REPL, after doing the above:
user=> (pr-str {:foo (ref 1)})
"{:foo #ref 1}"
当然你只需要print-method
在序列化的时候定义好方法即可;您正在反序列化代码可以不用管它,但需要适当的数据阅读器。
暂时忽略代码大小问题,因为我发现数据嵌入问题很有趣:
假设您的数据结构仅包含由 Clojure 本地处理的不可变数据(Clojure 持久集合,任意嵌套,加上原子项目,如数字、字符串(用于此目的的原子)、关键字、符号;没有 Refs 等),您确实可以包含它在您的代码中:
(defmacro embed [x]
x)
x
然后,通过使用类文件中包含的常量和类的静态方法clojure.lang.RT
(例如RT.vector
和RT.map
),生成的字节码将在不读取任何内容的情况下重新创建。
当然,这就是文字的编译方式,因为上面的宏是一个 noop。我们可以让事情变得更有趣:
(ns embed-test.core
(:require [clojure.java.io :as io])
(:gen-class))
(defmacro embed-resource [r]
(let [r (io/resource r)
rdr (java.io.PushbackReader. (io/reader r))]
(read r)))
(defn -main [& args]
(println (embed-resource "foo.edn")))
这将在编译时读取foo.edn
并将结果嵌入到编译代码中(在某种意义上,包括适当的常量和代码以在类文件中重建数据)。在运行时,不会执行进一步的读取。