【发布时间】:2016-10-22 14:11:51
【问题描述】:
我想解析一个大的 json 文件 (3GB) 并为该文件中的每一行返回一个哈希映射。我的直觉是使用传感器逐行处理文件并构造一个带有一些选定字段的向量(> 文件中的 5% 字节)。
但是,以下代码会引发 OutOfMemory 异常:
文件.json
{"experiments": {"results": ...}}
{"experiments": {"results": ...}}
{"experiments": {"results": ...}}
parser.clj
(defn load-with!
"Load a file using a parser, a structure and a transducer."
[parser structure xform path]
(with-open [r (clojure.java.io/reader path)]
(into structure xform (parser r))))
(def xf (map #(get-in % ["experiments" "results"])))
(def parser (comp (partial map cheshire.core/parse-string) line-seq))
(load-with! parser (vector) xf "file.json")
当我使用 JVisualVM 可视化进程时,堆会随着时间的推移而增长,并在进程崩溃之前超过 25 GB。
在这种情况下传感器是否合适?有更好的选择吗?
我的要求之一是在函数末尾返回新结构。因此,我无法使用 doseq 就地处理文件。
另外,我需要根据文件格式更改解析器和转换器。
谢谢!
【问题讨论】:
-
我不完全理解你的代码。解析器的作用是什么?它似乎已通过但未使用。此外,
(r)表达式可能不是您想要的,它将 reader 称为函数。 -
我不明白为什么传感器会有所帮助。当您要对数据执行一系列操作时,转换器很有用;转换器允许您避免创建将被丢弃的中间数据结构。这段代码只做一件事——它映射
get-in。注意into是非懒惰的。你能懒惰地处理文件吗?使用for、map或sequence转换器函数,您能创建一个惰性映射条目序列吗?如果处理得当,您可以处理每一个,而无需将所有文件内容保存在内存中。 -
解析器/转换器的目标是根据文件格式(例如 json、csv ...)和文件中的供应商格式轻松调整工作。
-
您能否详细说明 JSON 文件中的数据,例如行数和每行的大小?或者,更好的是,将文件的代表性版本上传到某个地方,以便我们可以准确地重现问题?我在一个非常小的文件上尝试了您的代码并且效果很好,但我希望它会中断,因为从 3G 文件中获取 25G 的内存使用表明某种无限循环或其他东西。
-
@Mars 是的,在这种特殊情况下,xform 并没有做太多。但是,对于不同的文件,您可能希望应用一些过滤以及一些获取操作,在这种情况下加载! fn 接受 xform 绝对有用。至于懒惰地处理文件,据我所知 should 是因为 line-seq 是懒惰的,map 也是如此,但 OOM 错误显然表明某处出了问题。当然
into是非懒惰的,但load-with!必须返回一些非懒惰的东西,我认为关键是提取的数据应该适合内存。
标签: clojure transducer