7

到目前为止,我有:

(: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是懒惰的,并且rdrin(csv/parse-csv rdr)))是 csv 文件的单行,对吗?

PS。我也想搜索文件,重复打开文件(即使它被懒惰地读取)并搜索整个内容是否昂贵?

4

4 回答 4

13

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)
于 2013-10-28T21:35:59.180 回答
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 数据分析食谱

于 2013-10-29T11:19:00.930 回答
1

问题是您的process-csv函数并没有真正“处理”范围的CSV 数据with-open,而是将其作为惰性序列返回。当执行退出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 协议,看看它们在与上述类似的场景中抽象资源方面有何用处。

于 2015-01-20T14:54:51.430 回答
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)))))

如果文件很大,我不认为多次打开文件会比在里面搜索内容昂贵。

如果您需要多次执行此操作,我会考虑构建某种搜索索引。

于 2013-10-28T21:19:33.660 回答