如何在没有流被关闭之前正确地打开处理?

时间:2018-02-10 17:10:05

标签: clojure

我正在撰写我的第一个Clojure计划。

我正在使用clojure.data.csv来处理csv文件。我的文件可能很大,所以我确实想利用懒惰。我的MWE代码演示了我的问题如下所示。

当我执行load-data函数时,我得到“IOException Stream closed”,所以我很清楚懒惰流在消费点之前被关闭了。

我已经查看了data.csv(https://github.com/clojure/data.csv)的文档,并且可以看到在使用之前阻止流被关闭的一种方法是将流开放移动到使用流的callstack。据我了解,这是我在下面所做的,因为(采取5)是在开放的范围内。显然,我有一个概念上的差距。非常感谢任何帮助!

(ns data-load.core
  (:gen-class)
  (:require [clojure.data.csv :as csv]
            [clojure.java.io :as io]))

(defn load-data [from to]
   (with-open [reader (io/reader from)
               writer (io/writer to)]
              (->> (csv/read-csv reader)
              (take 5))))

2 个答案:

答案 0 :(得分:7)

正如您所说,您从load-data返回的内容是一个懒惰的序列,当它被消耗时,您已经离开了with-open的范围。你只需要在返回之前强制实现延迟序列。

  

据我了解,这是我在下面所做的,因为(take 5)属于with-open的范围。

它在范围内,但take也返回一个懒惰的序列!它只在另一个中包含了一个懒惰的序列,直到with-open范围之后才能实现。来自clojure.data.csv示例:

(defn sum-second-column [filename]
  (with-open [reader (io/reader filename)]
    (->> (read-column reader 1)
         (drop 1)
         (map #(Double/parseDouble %))
         (reduce + 0)))) ;; this is the only non-lazy operation

这里重要的观察是最终操作是reduce,它将消耗懒惰序列。如果您将reduce取出并试图从函数外部使用生成的序列,那么您将获得相同的"流关闭"异常。

执行此操作的一种方法是将序列转换为带vec的向量,或使用doall,这也会强制实现:

(defn load-data [from]
  (with-open [reader (io/reader from)]
   (->> (csv/read-csv reader)
        (take 5)
        ;; other intermediate steps go here
        (doall))))
  

我的文件可能很大,所以我确实想利用懒惰。

在流关闭之前,您需要一种方法来完成所有工作,因此您可以为load-data函数提供一个功能,以便在CSV的每一行上执行:

(defn load-data [from f]
  (with-open [reader (io/reader from)]
    (doall (map f (csv/read-csv reader)))))

例如,将行值连接成字符串:

(load-data (io/resource "input.txt")
           (partial apply str))
=> ("abc" "efg")

答案 1 :(得分:0)

如果你想要一个懒惰的解决方案,那么请查看https://stackoverflow.com/a/13312151/954570(所有积分将转到原作者https://stackoverflow.com/users/181772/andrew-cookehttps://stackoverflow.com/users/611752/johnj)。

这个想法是手动管理阅读器打开/关闭并保持阅读器打开,直到序列耗尽。它有自己的怪癖但对我来说效果很好(我需要合并/处理多个不适合内存的大文件中的数据)。