由于 JVM 的大小限制,可能无法在编译后的代码中嵌入这么大的数据。特别是,任何一种方法的长度都不能超过 64 KiB。以我在下面进一步描述的方式嵌入数据也需要在它将要存在的类文件中包含大量内容;似乎不是一个好主意。
鉴于您使用的是只读数据结构,您可以构造一次,然后将其发送到 .clj / .edn(这是针对 edn,基于 Clojure 文字符号的序列化格式) ,然后将该文件作为“资源”包含在您的类路径中,以便它包含在 überjar 中(在 resources/ 中,具有默认 Leiningen 设置;然后它将被包含在 überjar 中,除非在 @987654326 中被 :uberjar-exclusions 排除@) 并在运行时以 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)
然后,生成的字节码将通过使用类文件中包含的常量和clojure.lang.RT 类的静态方法(例如RT.vector 和RT.map),在不读取任何内容的情况下重新创建x。
当然,这是文字的编译方式,因为上面的宏是一个 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,并将结果嵌入到编译代码中(在某种意义上,包括适当的常量和代码以在类文件中重建数据)。在运行时,不会执行进一步的读取。