Clojure读取大文件并在其中转换数据的方式

时间:2013-04-14 05:12:06

标签: file-io clojure functional-programming iterator

我正在处理一个非常大的Subrip字幕文件,需要一次处理一个字幕。在Java中,要从文件中提取字幕,我会编写一个带有以下签名的方法:

Iterator<Subtitle> fromSubrip(final Iterator<String> lines);

Iterator的使用给了我两个好处:

  1. 该文件从不在整个内存中,也不是任何转换过的阶段。
  2. 一种抽象,我可以在没有内存开销的情况下循环遍历Subtitle个对象的集合。
  3. 由于迭代器本质上是命令式和可变的,因此它们在Clojure中可能不是惯用的。那么处理这种情况的Clojure方法是什么?

3 个答案:

答案 0 :(得分:3)

正如弗拉基米尔所说,你需要正确处理懒惰和文件关闭。我是这样做的,如“Read a very large text file into a list in clojure”所示:

(defn lazy-file-lines 
  "open a (probably large) file and make it a available as a lazy seq of lines"
  [filename]
  (letfn [(helper [rdr]
                  (lazy-seq
                    (if-let [line (.readLine rdr)]
                      (cons line (helper rdr))
                      (do (.close rdr) nil))))]
         (helper (clojure.java.io/reader filename))))

答案 1 :(得分:2)

从目录中读取所有文件,这是一种懒惰的方式。

使用go black和channel。

代码:

(ns user
  (:require [clojure.core.async :as async :refer :all 
:exclude [map into reduce merge partition partition-by take]]))

(defn read-dir [dir]
  (let [directory (clojure.java.io/file dir)
        files (filter #(.isFile %) (file-seq directory))
        ch (chan)]
    (go
      (doseq [file files]
        (with-open [rdr (clojure.java.io/reader file)]
          (doseq [line (line-seq rdr)]
            (>! ch line))))
      (close! ch))
    ch))

调用:

(def aa "D:\\Users\\input")
(let [ch (read-dir aa)]
  (loop []
    (when-let [line (<!! ch )]
      (println line)
      (recur))))

=====

reify Iterable interace,可以在java中使用。

MyFiles.clj:
(ns user
  (:gen-class :methods [#^{:static true} [readDir [String] Iterable]])
  (:require [clojure.core.async :as async :refer :all 
:exclude [map into reduce merge partition partition-by take]]))

(defn -readDir [dir]
  (def i nil)
  (let [ch (read-dir dir)
        it (reify java.util.Iterator
             (hasNext [this] (alter-var-root #'i (fn [_] (<!! ch))) (not (nil? i)))
             (next [this] i))
        itab (reify Iterable
               (iterator [this] it))]
    itab))

java代码:

for (Object line : MyFiles.readDir("/dir")) {
    println(line)
}

答案 2 :(得分:1)

您可以使用延迟序列,例如line-seq

但是,您必须小心line-seq(以及基于某些外部资源返回延迟序列的其他函数)返回的序列永远不会泄漏出来。 with-open范围,因为在源关闭后,进一步读取延迟序列将导致异常。