我正在处理60GB或更大的文本文件。文件分为可变长度的标题部分和数据部分。我有三个功能:
head?
区分标题行和数据行的谓词process-header
处理一个标题行字符串process-data
处理一个数据线串- 处理函数异步访问和修改内存中的数据库
我从另一个SO线程中改进了一个文件读取方法,该方法应该构建一个延迟的行序列。这个想法是用一个函数处理一些行,然后切换一次函数,然后用下一个函数继续处理。
(defn lazy-file
[file-name]
(letfn [(helper [rdr]
(lazy-seq
(if-let [line (.readLine rdr)]
(cons line (helper rdr))
(do (.close rdr) nil))))]
(try
(helper (clojure.java.io/reader file-name))
(catch Exception e
(println "Exception while trying to open file" file-name)))))
我把它和一起使用
(let [lfile (lazy-file "my-file.txt")]
(doseq [line lfile :while head?]
(process-header line))
(doseq [line (drop-while head? lfile)]
(process-data line)))
尽管这是有效的,但由于以下几个原因,它的效率相当低:
- 我不需要简单地调用
process-head
,直到我到达数据,然后继续使用process-data
,而是必须过滤标题行并处理它们,然后重新开始解析整个文件,并删除所有标题行来处理数据。这与lazy-file
的意图完全相反 - 观察内存消耗告诉我,这个程序虽然看起来很懒,但它的内存消耗量与将文件保存在内存中所需的内存一样多
那么,使用数据库的更高效、更惯用的方法是什么呢?
一种想法可能是使用多方法来处理依赖于head?
谓词值的标头和数据,但我认为这会对速度产生严重影响,尤其是在只有一次谓词结果从始终为true变为始终为false的情况下。我还没有对此进行基准测试。
使用另一种方法构建行seq并用iterate
解析它会更好吗?我想,这仍然会让我需要使用:while和:drop while。
在我的研究中,曾多次提到使用NIO文件访问,这将提高内存使用率。我还不知道如何在clojure中以惯用的方式使用它。
也许我还是有一个不好的总体思路,应该如何对待档案?
一如既往,我们非常感谢任何帮助、想法或指导。
您应该使用标准库函数。
行seq,与开放和doseq将很容易做到这一点。
(with-open [rdr (clojure.java.io/reader file-path)]
(doseq [line (line-seq rdr)]
(if (head? line)
(process-header line)
(process-data line))))
这里需要考虑以下几点:
-
内存使用
有报道称,leiningen可能会添加一些内容,从而保留对头部的引用,尽管doseq特别没有保留它正在处理的序列的头部,参见这个SO问题。尝试验证您的索赔";使用与将文件保持在存储器中所需的RAM一样多的RAM";而不使用CCD_ 9。
-
分析线路
您也可以使用
loop/recur
方法,而不是使用doseq
的两个循环。您期望解析的是第二个参数,如下所示(未经测试):(loop [lfile (lazy-file "my-file.txt") parse-header true] (let [line (first lfile)] (if [and parse-header (head? line)] (do (process-header line) (recur (rest lfile) true)) (do (process-data line) (recur (rest lfile) false)))))
这里还有另一个选项,就是将处理功能合并到文件读取功能中。因此,与其只是
cons
生成一个新行并返回它,您还可以立即处理它——通常情况下,您可以将处理函数作为参数,而不是对其进行硬编码您当前的代码看起来处理是一种副作用。如果是这样的话,那么如果你结合了处理,你可能会消除懒惰。无论如何,您都需要处理整个文件(或者看起来是这样),并且是按行处理的。
lazy-seq
方法基本上只是将单行读取与单个处理调用对齐。在当前的解决方案中,您需要懒惰,因为您将读取(整个文件,逐行)与处理分开。如果你把一行的处理转移到阅读中,你就不需要懒散地这样做。