我试图通过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
调用适用于所有值< = 200。
知道发生了什么事吗?
答案 0 :(得分:9)
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)))))
或类似的东西。请记住,在搜索指定的行时,你的多个nth保持行seq的头部,这意味着它将保持所有行直到最后指定的行在内存中 - 并且使用nth就像这意味着你为每个索引反复踩过line-seq - 你需要解决这个问题。
更新
这样的东西会起作用。这比我喜欢的有点丑,但它显示了原理,我认为:请注意,这里的指数必须是设置。
(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
答案 1 :(得分:5)
with-file
一旦执行了正文,就会关闭文件。因此,一旦multi-nth
被执行,文件就会关闭,这意味着你最终会得到一个指向已关闭文件的延迟序列。
(read-lines "my-file" [0])
有效,因为只实现了延迟序列的第一个元素。
要解决此问题,您需要强制使用doall
实现序列:
(defn multi-nth [values indices]
(doall (map (partial nth values) indices)))