使用clojure-csv.core解析一个巨大的csv文件

时间:2013-10-28 21:12:11

标签: csv clojure bigdata

到目前为止,我有:

(: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。我也想搜索文件,重复打开文件是否很昂贵(即使它是懒惰地阅读)并搜索整个内容?

4 个答案:

答案 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)))))

我不认为多次打开文件会比在文件很大的情况下搜索内容要贵。

如果您需要多次这样做,我会考虑构建某种搜索索引。