Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/file/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
File 将非常大的文本文件读入clojure中的列表_File_Text_Clojure - Fatal编程技术网

File 将非常大的文本文件读入clojure中的列表

File 将非常大的文本文件读入clojure中的列表,file,text,clojure,File,Text,Clojure,在clojure中将一个非常大的文件(比如一个每行有10万个名称的文本文件)读入一个列表(根据需要延迟加载)的最佳方式是什么 基本上,我需要对这些项目进行各种字符串搜索(我现在使用shell脚本中的grep和reg-ex进行搜索) 我尝试在开头和结尾添加',但显然这种方法(加载静态?/常量列表)由于某些原因有大小限制。您需要使用。clojuredocs中的一个示例: ;; Count lines of a file (loses head): user=> (with-open [rdr

在clojure中将一个非常大的文件(比如一个每行有10万个名称的文本文件)读入一个列表(根据需要延迟加载)的最佳方式是什么

基本上,我需要对这些项目进行各种字符串搜索(我现在使用shell脚本中的grep和reg-ex进行搜索)

我尝试在开头和结尾添加',但显然这种方法(加载静态?/常量列表)由于某些原因有大小限制。

您需要使用。clojuredocs中的一个示例:

;; Count lines of a file (loses head):
user=> (with-open [rdr (clojure.java.io/reader "/etc/passwd")]
         (count (line-seq rdr)))
但是,使用惰性字符串列表,您无法有效地执行那些需要显示整个列表的操作,例如排序。如果您可以将操作实现为
过滤器
映射
,则可以惰性地使用列表。否则,最好使用嵌入式数据库

还请注意,您不应抓住列表的开头,否则整个列表将加载到内存中


此外,如果需要执行多个操作,则需要一次又一次地读取文件。请注意,懒惰有时会使事情变得困难。

根据您的具体需要,有多种方法可以做到这一点

如果有一个
函数
要应用于文件中的每一行,可以使用类似于Abhinav答案的代码:

(with-open [rdr ...]
  (doall (map function (line-seq rdr))))
这样做的好处是,可以尽快打开、处理和关闭文件,但会强制一次性使用整个文件

如果要延迟文件处理,可能会尝试返回行,但这不起作用:

因为当
with open
返回时,文件是关闭的,这是在您懒散地处理文件之前

一种解决方法是使用
slurp
将整个文件拉入内存:

(map function (slurp filename))
这有一个明显的缺点-内存使用-但可以保证文件不会保持打开状态

另一种方法是将文件保持打开状态,直到读取结束,同时生成延迟序列:

(ns ...
  (:use clojure.test))

(defn stream-consumer [stream]
  (println "read" (count stream) "lines"))

(defn broken-open [file]
  (with-open [rdr (clojure.java.io/reader file)]
    (line-seq rdr)))

(defn lazy-open [file]
  (defn helper [rdr]
    (lazy-seq
      (if-let [line (.readLine rdr)]
        (cons line (helper rdr))
        (do (.close rdr) (println "closed") nil))))
  (lazy-seq
    (do (println "opening")
      (helper (clojure.java.io/reader file)))))

(deftest test-open
  (try
    (stream-consumer (broken-open "/etc/passwd"))
    (catch RuntimeException e
      (println "caught " e)))
  (let [stream (lazy-open "/etc/passwd")]
    (println "have stream")
    (stream-consumer stream)))

(run-tests)
其中打印:

caught  #<RuntimeException java.lang.RuntimeException: java.io.IOException: Stream closed>
have stream
opening
closed
read 29 lines
捕获#
有小溪
开放
关闭
读29行
显示文件在需要时才被打开

最后一种方法的优点是,您可以在“其他地方”处理数据流,而无需将所有内容都保存在内存中,但它也有一个重要的缺点:在读取流的末尾之前,文件不会关闭。如果不小心,您可能会并行打开许多文件,甚至忘记关闭它们(通过不完全读取流)

最佳选择取决于具体情况——这是在惰性评估和有限的系统资源之间进行权衡


PS:lazy open是否定义在库中的某个地方?我试图找到这样一个函数来解决这个问题,并最终编写了自己的函数,如上所述。

Andrew的解决方案对我来说效果很好,但是嵌套的
defn
并不是那么习惯用法,您不需要重复执行
lazy seq
两次:这是一个带有ut额外打印并使用
letfn

(defn lazy-file-lines [file]
  (letfn [(helper [rdr]
                  (lazy-seq
                    (if-let [line (.readLine rdr)]
                      (cons line (helper rdr))
                      (do (.close rdr) nil))))]
         (helper (clojure.java.io/reader file))))

(count (lazy-file-lines "/tmp/massive-file.txt"))
;=> <a large integer>
(定义惰性文件行[文件]
(letfn[(助手[rdr]
(续)
(如果let[line(.readLine rdr)]
(cons线路(辅助设备rdr))
(do(.close rdr)nil))]
(助手(clojure.java.io/reader文件)))
(计数(惰性文件行“/tmp/massive file.txt”))
;=> 

因此:

(def aa“D:\\Users\\input)
(让[ch(读目录aa)]
(循环[]

(当let[line(您可能会发现该库对于在Clojure中处理非常大的文件非常有用。当我对大量输入应用还原程序时,我一直使用iota序列,iota/vec通过索引它们提供对大于内存的文件的随机访问。

非常感谢,但是如果我想将所有列表保留在内存中该怎么办(不要偷懒),那么最好的方法是什么呢?正如您所说的,对于一些操作,我需要一遍又一遍地检查列表(假设我有足够的内存来保存整个列表)。在这种情况下,只需保留对偷懒列表头的引用。它将第一次被偷懒加载,然后保持加载状态。类似于:
(def names(使用open[rdr(clojure.java.io/reader)/path/to/names/file”)](line-seq-rdr))
好吧,我不这么认为。因为您已经用“with-open”包围了“line-seq”,底层流在返回时将自动关闭。因此,您的“names”变量后面没有留下任何东西。因此基本上您必须使用1:
(def-rdr)(clojure.java.io/reader“/path/to/names/file”)
2:
(def names(line seq rdr))
3:
(.rdr close)
。最后,你现在可以玩你的“名字”了,比如:
(count names)
@RolloTomazzi,如果你在关闭
rdr
之前没有意识到
名字,它也不会起作用(问题与@AbhinavSarkar的建议完全相同:
line seq
只读取第一个元素,其余元素是惰性的,因此关闭
rdr
将不允许您读取
name
的第一个元素,因此
(count names)
可能会引发异常)。您必须在2和3之间添加一个新步骤,以实现集合,如
(dorun name)
。但是,这相当于
(def name(带有open[rdr…](doall(line seq rdr)))
,就像@andrew的答案一样,这是更好的方法。使用
循环
重现
会更好@NeloMitranim
循环
/
重现
并不懒惰。嗯,对不起,我的错。我没听清楚。
(defn lazy-file-lines [file]
  (letfn [(helper [rdr]
                  (lazy-seq
                    (if-let [line (.readLine rdr)]
                      (cons line (helper rdr))
                      (do (.close rdr) nil))))]
         (helper (clojure.java.io/reader file))))

(count (lazy-file-lines "/tmp/massive-file.txt"))
;=> <a large integer>
(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))))