【问题标题】:Lazily extract lines from large file懒惰地从大文件中提取行
【发布时间】:2012-08-13 07:30:24
【问题描述】:

我正在尝试使用 Clojure 从一个大 (> 1GB) 文件中按行号获取 5 行。我快到了,但看到一些奇怪的东西,我想了解发生了什么。

到目前为止,我得到了:

(defn multi-nth [values indices]
  (map (partial nth values) indices))

(defn read-lines [file indices]
  (with-open [rdr (clojure.java.io/reader file)]
    (let [lines (line-seq rdr)]
      (multi-nth lines indices))))

现在,(read-lines "my-file" [0]) 可以正常工作。但是,传入[0 1] 会给我以下堆栈跟踪:

java.lang.RuntimeException: java.io.IOException: Stream closed
        Util.java:165 clojure.lang.Util.runtimeException
      LazySeq.java:51 clojure.lang.LazySeq.sval
      LazySeq.java:60 clojure.lang.LazySeq.seq
         Cons.java:39 clojure.lang.Cons.next
          RT.java:769 clojure.lang.RT.nthFrom
          RT.java:742 clojure.lang.RT.nth
         core.clj:832 clojure.core/nth
         AFn.java:163 clojure.lang.AFn.applyToHelper
         AFn.java:151 clojure.lang.AFn.applyTo
         core.clj:602 clojure.core/apply
        core.clj:2341 clojure.core/partial[fn]
      RestFn.java:408 clojure.lang.RestFn.invoke
        core.clj:2430 clojure.core/map[fn]

似乎在我从文件中读取第二行之前,流已被关闭。有趣的是,如果我用(nth lines 200) 之类的内容手动从文件中拉出一行,multi-nth 调用适用于所有值

知道发生了什么吗?

【问题讨论】:

    标签: clojure lazy-evaluation stream-processing


    【解决方案1】:

    map(和 line-seq)返回惰性序列,因此当您调用 with-open 返回时,没有必要读取任何行,这会关闭文件。

    基本上需要在with-open返回之前实现整个返回值,可以使用doall:

    (defn multi-nth [values indices]
      (map (partial nth values) indices))
    
    (defn read-lines [file indices]
      (with-open [rdr (clojure.java.io/reader file)]
        (let [lines (line-seq rdr)]
          (doall (multi-nth lines indices)))))
    

    或类似的东西。请记住,在搜索指定行时,您的 multi-nth 会保留行 seq 的开头,这意味着它将保持所有行直到内存中的最后一个指定行 - 并且像这样使用 nth 意味着您'正在为每个索引重复遍历 line-seq - 您需要修复它。

    更新:

    这样的事情会奏效。它比我喜欢的要丑一些,但它显示了原理,我认为:注意这里的索引需要是一个 set

    (defn multi-nth [values indices]
     (keep 
       (fn [[number line]] 
         (if (contains? indices number) 
           line))
       (map-indexed vector values)))
    
    (multi-nth '(a b c d e) #{2 3})
      => c d
    

    【讨论】:

    • 好点。我是否需要使用较低级别的 Java 随机访问方法调用才能正确处理?
    • 我认为您可以使用地图索引和过滤器获得相当清晰的代码。我会在一分钟内更新...
    • 啊,好的,太好了。如果它在语法上比您想要的更丑陋,您可以使用 keep-indexed 并可能压缩过滤器功能。现在正在研究它......
    【解决方案2】:

    with-file 在文件主体执行后关闭文件。因此,一旦multi-nth 被执行,文件就会关闭,这意味着您最终会得到一个指向已关闭文件的惰性序列。

    (read-lines "my-file" [0]) 之所以有效,是因为只实现了惰性序列的第一个元素。

    要解决此问题,您需要使用doall 强制实现序列:

    (defn multi-nth [values indices]
      (doall (map (partial nth values) indices)))
    

    更详细的解释见https://stackoverflow.com/a/10462159/151650

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-09
      • 2012-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-19
      • 2020-11-04
      • 1970-01-01
      相关资源
      最近更新 更多