2010-11-07 14 views
25

¿Cuál es la mejor manera de leer un archivo muy grande (como un archivo de texto con 100 000 nombres, uno en cada línea) en una lista (perezosamente, cargándolo según sea necesario) en clojure?Lea un archivo de texto muy grande en una lista en clojure

Básicamente tengo que hacer todo tipo de búsquedas de cadenas en estos artículos (ahora lo hago con grep y reg ex en scripts de shell).

He intentado añadir '(al principio y) al final pero al parecer este método (carga de un?/Lista estática de constante, tiene una limitación de tamaño, por alguna razón.

Respuesta

19

Es necesario utilizar line-seq. Un ejemplo de clojuredocs:

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

Pero con una lista perezosa de cadenas, no se puede hacer esas operaciones de manera eficiente que requieren toda la lista que estén presentes, como la clasificación Si se puede poner en práctica sus operaciones como filter o map entonces se puede consumir. la lista perezosamente. De lo contrario, será mejor usar una inserción base de datos ded

También tenga en cuenta que no debe mantener el encabezado de la lista, de lo contrario, toda la lista se cargará en la memoria.

Además, si necesita hacer más de una operación, necesitará leer el archivo una y otra vez. Se advirtió, la pereza puede hacer las cosas difíciles a veces.

+0

Muchas gracias, pero ¿y si quisiera mantener toda la lista en la memoria (no ser flojo), cuál sería la mejor manera entonces? Como dijiste para algunas operaciones, necesito repasar la lista una y otra vez (supongamos que tengo suficiente memoria para mantener toda la lista). – Ali

+3

En ese caso, simplemente mantenga una referencia al encabezado de la lista diferida. Se cargará perezosamente la primera vez y luego se mantendrá cargado. Algo así como: '(nombres de definición (with-open [rdr (clojure.java.io/reader"/ruta/a/nombres/archivo ")] (línea-seq rdr)))' –

+6

Bueno, no creo asi que. Como ha rodeado "line-seq" con "with-open", la transmisión subyacente se cerrará automáticamente cuando regrese. Entonces no queda nada detrás de tus "nombres" var. Entonces, básicamente, tendría que 1: '(def rdr (clojure.java.io/reader"/ruta/a/nombres/archivo "))' luego 2: '(nombres de definición (line-seq rdr))' luego 3 : '(. rdr close)'. Finalmente, ahora puedes jugar con tus "nombres" como: '(nombres de conteo)' –

27

Existen varias formas de hacerlo, dependiendo de lo que desee exactamente.

Si usted tiene un function que desea aplicar a cada línea en un archivo, se puede utilizar un código similar a la respuesta de Abhinav:

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

Esto tiene la ventaja de que se abra el archivo, se procesa y cerrado lo más rápido posible, pero obliga a consumir todo el archivo de una vez.

Si desea retrasar el procesamiento del archivo que podría estar tentado a regresar las líneas, pero esto no funcionará:

(map function ; broken!!! 
    (with-open [rdr ...] 
     (line-seq rdr))) 

porque el archivo se cierra cuando with-open devoluciones, que es antes de, procesa el archivo de forma perezosa.

Una forma de evitar esto es para tirar todo el archivo en memoria con slurp:

(map function (slurp filename)) 

que tiene una desventaja obvia - el uso de memoria - pero garantiza que no dejar el archivo abierto.

Una alternativa es dejar el archivo abierto hasta llegar al final de la lectura, al tiempo que genera una secuencia perezosa:

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

que imprime:

caught #<RuntimeException java.lang.RuntimeException: java.io.IOException: Stream closed> 
have stream 
opening 
closed 
read 29 lines 

mostrando que el wasn de archivos incluso se abrió hasta que fue necesario.

Este último enfoque tiene la ventaja de que puede procesar la secuencia de datos "en otro lugar" sin guardar todo en la memoria, pero también tiene una desventaja importante: el archivo no se cierra hasta que se lee el final de la secuencia. Si no tiene cuidado, puede abrir muchos archivos en paralelo, o incluso olvidarse de cerrarlos (al no leer la secuencia por completo).

La mejor elección depende de las circunstancias: se trata de una compensación entre la evaluación diferida y los recursos limitados del sistema.

PD: ¿Se ha definido lazy-open en algún lugar de las bibliotecas? Llegué a esta pregunta tratando de encontrar esa función y terminé escribiendo la mía, como se indica arriba.

18

solución de Andrew funcionado bien para mí, pero anidados defn s no son tan idiomático, y que no es necesario hacer lazy-seq dos veces: aquí es una versión actualizada sin las copias adicionales y el uso de 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> 
+0

Esto sería mejor con 'loop' y' recur'. –

+0

@NeloMitranim 'loop' /' recur' no es flojo. – JohnJ

+0

Hmm lo siento, mi mal. No entendí bien eso. –

1

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

manera:

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