代码之家  ›  专栏  ›  技术社区  ›  Ali

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

  •  26
  • Ali  · 技术社区  · 14 年前

    在clojure中,将一个非常大的文件(比如一个每行有10万个名字的文本文件)读入一个列表(根据需要懒洋洋地加载)的最佳方法是什么?

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

    4 回复  |  直到 14 年前
        1
  •  21
  •   Abhinav Sarkar    14 年前

    你需要使用 line-seq . clojuredocs的一个例子:

    ;; Count lines of a file (loses head):
    user=> (with-open [rdr (clojure.java.io/reader "/etc/passwd")]
             (count (line-seq rdr)))
    

    filter map 然后你就可以懒洋洋地消费清单了。否则最好使用嵌入式数据库。

    还要注意的是,您不应该抓住列表的头部,否则整个列表将加载到内存中。

        2
  •  30
  •   Brad Koch Daniel Wright    11 年前

    有多种方法可以做到这一点,具体取决于你想要什么。

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

    (with-open [rdr ...]
      (doall (map function (line-seq rdr))))
    

    这样做的好处是文件可以尽快打开、处理和关闭,但会强制立即使用整个文件。

    这不管用 :

    (map function ; broken!!!
        (with-open [rdr ...]
            (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
    

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

    最好的选择取决于环境——这是懒惰的评估和有限的系统资源之间的权衡。

    PS:是 lazy-open

        3
  •  22
  •   JohnJ    12 年前

    安德鲁的解决方案对我很有效,但是 defn s不是那么惯用,你不需要这么做 lazy-seq 两次:这是一个更新版本,没有额外的打印和使用 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>
    
        4
  •  1
  •   Community CDub    8 年前

    see my answer here

    (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))))