到目前为止,我有:
(:require [clojure-csv.core :as csv])
(:require [clojure.java.io :as io]))
(def csv-file (.getFile (clojure.java.io/resource "verbs.csv")))
(defn process-csv [file]
(with-open [rdr (io/reader file)]
(csv/parse-csv rdr)))
但我得到了java.io.IOException: Stream closed
。我正在使用clojure-csv,它暴露了两种方法,我使用的第一种方法parse-csv
,文档说:
Takes a CSV as a char sequence or string, and returns a lazy sequence of vectors of strings
我认为我知道:with-open
是懒惰的,rdr
中的(csv/parse-csv rdr)))
是csv文件的一行吗?
PS。我也想搜索文件,重复打开文件是否很昂贵(即使它是懒惰地阅读)并搜索整个内容?
答案 0 :(得分:11)
with-open
不是懒惰的,但是如果你在with-open
内做了一些懒惰的事情,如果懒惰的动作没有被强加到with-open
的范围内,你就会遇到问题。需要做的是在退出with-open
块之前强制执行所有延迟结果。
(defn process-csv [file]
(with-open [rdr (io/reader file)]
(doall (csv/parse-csv rdr))))
函数doall
旨在确保实现整个延迟序列。
由于您输入的大小,另一种可能性是安排自己关闭阅读器,然后将惰性用于其预期目的(仅在您需要时生成结果)。
(defn find-results
[stream]
(for [record stream
:while (seq (first record))]
record))
(def rdr (io/reader "verbs.csv"))
(def csv (csv/parse-csv rdr))
(def results (doall (find-results csv)))
(.close rdr)
答案 1 :(得分:7)
我知道这已经回答了,但是这里有一个与@noisesmith类似的解决方案,它创建了一个显式的延迟序列,如果你到达输入的末尾则会自动关闭。
如果您要懒得处理整个文件,这意味着您不必自己明确管理句柄,否则您会遇到公开处理问题。
(defn lazy-read-csv
[csv-file]
(let [in-file (io/reader csv-file)
csv-seq (csv/read-csv in-file)
lazy (fn lazy [wrapped]
(lazy-seq
(if-let [s (seq wrapped)]
(cons (first s) (lazy (rest s)))
(.close in-file))))]
(lazy csv-seq)))
这是来自Eric Rochester的优秀Clojure Data Analysis Cookbook
答案 2 :(得分:1)
问题是您的process-csv
函数并未真正“处理” with-open
范围内的CSV数据,而是将其作为延迟序列返回。当执行退出with-open
范围时,流已经关闭。稍后尝试遍历延迟列表将抛出异常。
除非您确信CSV文件可以被读取并完全解析到内存中,否则我建议不要遵循其他答案中推荐的内容,即强制评估with-open
内的惰性序列范围使用doall
。
相反,如果您希望将资源分配和解除分配部分与“更可重用”的业务逻辑分开,则应该执行以下操作:
(defn process-csv [rdr conn]
(doseq [row (csv/parse-csv rdr) :where (wanted? row)]
(save-to-custom-database-table conn row)))
(defn start [process-fn]
(let [csv-file (.getFile (clojure.java.io/resource "verbs.csv"))]
(with-open [rdr (jio/reader csv-file)
conn (database-connection "TEST")]
(process-fn rdr conn))))
(start process-csv)
正如您所看到的,process-csv
函数以“抽象”的方式处理读者和数据库资源,即不会被这些资源Closeable
所困扰,应该在使用后关闭。相反,资源的完成/关闭在start
函数中作为单独的事项处理。
我还建议你研究一下Clojure协议,看看它们在如上所述的类似场景中抽象资源中是如何有用的。
答案 3 :(得分:0)
当文件已经关闭时,看起来文件正试图在with-open
表单之外懒洋洋地解析。
尝试这样的方法来验证,打印前5个解析的行:
(defn process-csv [file]
(with-open [rdr (io/reader file)]
(let [lines (csv/parse-csv rdr)]
(doseq [l (take 5 lines)]
(println l)))))
我不认为多次打开文件会比在文件很大的情况下搜索内容要贵。
如果您需要多次这样做,我会考虑构建某种搜索索引。