【问题标题】:How To Read Directory Of Files, Line by Line, Lazily in Clojure如何在 Clojure 中懒惰地逐行读取文件目录
【发布时间】:2026-02-13 03:15:02
【问题描述】:
(->> "/Users/micahsmith/printio/gooten-import-ai/jupyter/data"
     File.
     file-seq
     (filter #(-> ^File % .getAbsolutePath (str-contains? ".json")))
     (mapcat (fn [^File file]
            (with-open [ rdr (io/reader file)]
              (line-seq rdr)))))

我正在尝试懒惰地逐行读取 json 文件的目录,以便我可以懒惰地对数据执行操作。

我不断收到java.io.IOException: Stream closed——我怎样才能在不过早关闭阅读器的情况下使用它?

【问题讨论】:

  • 这里的答案应该很有用:*.com/a/6797629
  • 只是问一下:那些“json”文件可以逐行读取吗?例如它们更像是一个 json-object 一行?

标签: clojure io seq


【解决方案1】:

with-open 函数旨在阻止您这样做,因为文件句柄和其他操作系统资源是您应该小心处理而不是懒惰处理的事情。您打算在with-open 的动态范围内对文件内容进行所有处理。因此,您应该接受一个函数作为参数,而不是返回一个惰性序列,并在仍然在with-open 范围内的惰性序列上调用该函数。该函数当然不应该返回另一个惰性序列,而是在返回之前处理其整个输入。

所以这种东西的典型用法是这样的:

(defn process-file [filename process]
  (with-open [f (io/reader filename)]
    (process (line-seq f))))

当你有一个with-open 序列列表时,情况会稍微复杂一些——你不能只调用一次process。您可以做的一件事是返回对每个文件调用 process 的结果列表:

(defn process-files [filenames process]
  (for [filename filenames]
    (with-open [f (io/reader filename)]
      (process (line-seq f)))))

然后,如果您需要对其进行一些全局操作,您可以reduce 覆盖process-files 的结果。

【讨论】:

    【解决方案2】:

    问题是当程序退出它所包含的范围时,with-open 调用 .close,但此时未必已读取所有行。

    我的解决方案可能是一个不应该出现的滥用可憎,但这里的想法是:创建一个只调用.close的“lazy-seq”,并将它连接到line-seq的末尾列表:

    (defn lazy-lines [^File file]
      (let [rdr (io/reader file)]
        (lazy-cat (line-seq rdr)
                  (do (.close rdr)
                      nil)))) ; Explicit nil to indicate termination
    
    (defn get-lines [^String path]
      (->> path
           (File.)
           (file-seq)
           (filter #(-> ^File % (.getAbsolutePath) (clojure.string/includes? ".json")))
           (mapcat lazy-lines)))
    

    根据我对桌面上文件的快速测试,它似乎可以工作。如果您将println 添加到终止lazy-seq 中,它会按预期打印,因此文件正在被关闭。

    我很犹豫是否建议这个解决方案,因为它依赖于在惰性列表中执行副作用,由于显而易见的原因,我已经习惯于“感觉不对”。此方法的主要缺点是除非评估整个序列,否则文件不会关闭,并且文件将一直保持打开状态,直到到达末尾。不过考虑到这些限制,我看不出如何避免这两个问题。


    我意识到我使用 lazy-cat 有点错误。我有一个额外的、不必要的 lazy-seq 包装器。现在已经修好了。你也可以使用类似

    (apply concat (line-seq rdr)
                  (lazy-seq (do (.close rdr)
                                nil))))))
    

    而不是lazy-cat

    【讨论】: