Me gustaría saber la forma "recomendada" de leer y escribir un archivo en clojure 1.3.
- Cómo leer todo el archivo
- Cómo leer un archivo línea por línea
- Cómo escribir un nuevo archivo
- Cómo agregar una línea a un archivo existente
Me gustaría saber la forma "recomendada" de leer y escribir un archivo en clojure 1.3.
Respuestas:
Suponiendo que solo estamos haciendo archivos de texto aquí y no algunas cosas binarias locas.
Número 1: cómo leer un archivo completo en la memoria.
(slurp "/tmp/test.txt")
No se recomienda cuando es un archivo realmente grande.
Número 2: cómo leer un archivo línea por línea.
(use 'clojure.java.io)
(with-open [rdr (reader "/tmp/test.txt")]
(doseq [line (line-seq rdr)]
(println line)))
La with-open
macro se encarga de que el lector esté cerrado al final del cuerpo. La función de lector coacciona una cadena (también puede hacer una URL, etc.) en un BufferedReader
. line-seq
entrega una secuencia perezosa. Exigir el siguiente elemento de la secuencia perezosa resulta en una línea que se lee del lector.
Tenga en cuenta que desde Clojure 1.7 en adelante, también puede usar transductores para leer archivos de texto.
Número 3: cómo escribir en un nuevo archivo.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt")]
(.write wrtr "Line to be written"))
Nuevamente, with-open
se encarga de que BufferedWriter
esté cerrado al final del cuerpo. Writer coacciona una cadena en un BufferedWriter
, que se usa mediante la interoperabilidad java:(.write wrtr "something").
También podría usar spit
, lo contrario de slurp
:
(spit "/tmp/test.txt" "Line to be written")
Número 4: agregue una línea a un archivo existente.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt" :append true)]
(.write wrtr "Line to be appended"))
Igual que el anterior, pero ahora con la opción de agregar.
O de nuevo con spit
, lo contrario de slurp
:
(spit "/tmp/test.txt" "Line to be written" :append true)
PD: Para ser más explícito sobre el hecho de que estás leyendo y escribiendo en un archivo y no en otra cosa, primero puedes crear un objeto de archivo y luego convertirlo en un BufferedReader
o escritor:
(reader (file "/tmp/test.txt"))
;; or
(writer (file "tmp/test.txt"))
La función de archivo también está en clojure.java.io.
PS2: a veces es útil poder ver cuál es el directorio actual (así que "."). Puede obtener la ruta absoluta de dos maneras:
(System/getProperty "user.dir")
o
(-> (java.io.File. ".") .getAbsolutePath)
(with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))
rinde en IOException Stream closed
lugar de una colección de líneas. ¿Qué hacer? Sin embargo, estoy obteniendo buenos resultados con la respuesta de @satyagraha.
(with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
doseq
retornos nil
pueden conducir a tiempos tristes sin valor de retorno.
Si el archivo cabe en la memoria, puede leerlo y escribirlo con sorbo y escupir:
(def s (slurp "filename.txt"))
(s ahora contiene el contenido de un archivo como una cadena)
(spit "newfile.txt" s)
Esto crea newfile.txt si no sale y escribe el contenido del archivo. Si desea agregar al archivo, puede hacerlo
(spit "filename.txt" s :append true)
Para leer o escribir un archivo en línea, usaría el lector y escritor de Java. Están envueltos en el espacio de nombres clojure.java.io:
(ns file.test
(:require [clojure.java.io :as io]))
(let [wrtr (io/writer "test.txt")]
(.write wrtr "hello, world!\n")
(.close wrtr))
(let [wrtr (io/writer "test.txt" :append true)]
(.write wrtr "hello again!")
(.close wrtr))
(let [rdr (io/reader "test.txt")]
(println (.readLine rdr))
(println (.readLine rdr)))
; "hello, world!"
; "hello again!"
Tenga en cuenta que la diferencia entre slurp / spit y los ejemplos de lector / escritor es que el archivo permanece abierto (en las declaraciones let) en este último y la lectura y la escritura se almacenan en el búfer, por lo tanto, son más eficientes al leer / escribir repetidamente en un archivo.
Aquí hay más información: slurp spit clojure.java.io Java's BufferedReader Java's Writer
Con respecto a la pregunta 2, a veces se desea que la secuencia de líneas se devuelva como un objeto de primera clase. Para obtener esto como una secuencia perezosa, y todavía tener el archivo cerrado automáticamente en EOF, utilicé esta función:
(use 'clojure.java.io)
(defn read-lines [filename]
(let [rdr (reader filename)]
(defn read-next-line []
(if-let [line (.readLine rdr)]
(cons line (lazy-seq (read-next-line)))
(.close rdr)))
(lazy-seq (read-next-line)))
)
(defn echo-file []
(doseq [line (read-lines "myfile.txt")]
(println line)))
defn
sea un Clojure ideomático. Su read-next-line
, por lo que yo entiendo, es visible fuera de su read-lines
función. Es posible que hayas usado un (let [read-next-line (fn [] ...))
en su lugar.
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Para leer un archivo línea por línea, ya no necesita recurrir a la interoperabilidad:
(->> "data.csv"
io/resource
io/reader
line-seq
(drop 1))
Esto supone que su archivo de datos se mantiene en el directorio de recursos y que la primera línea es información de encabezado que se puede descartar.