Mi piacerebbe conoscere il modo "raccomandato" di leggere e scrivere un file in clojure 1.3.
- Come leggere l'intero file
- Come leggere un file riga per riga
- Come scrivere un nuovo file
- Come aggiungere una linea a un file esistente
Mi piacerebbe conoscere il modo "raccomandato" di leggere e scrivere un file in clojure 1.3.
Risposte:
Supponendo che stiamo facendo solo file di testo qui e non alcune cose binarie pazze.
Numero 1: come leggere un intero file in memoria.
(slurp "/tmp/test.txt")
Non raccomandato quando si tratta di un file molto grande.
Numero 2: come leggere un file riga per riga.
(use 'clojure.java.io)
(with-open [rdr (reader "/tmp/test.txt")]
(doseq [line (line-seq rdr)]
(println line)))
La with-open
macro si preoccupa che il lettore sia chiuso alla fine del corpo. La funzione reader impone una stringa (può anche fare un URL, ecc.) In a BufferedReader
. line-seq
offre un seq pigro. Esigere il prossimo elemento del seq pigro risulta in una riga che viene letta dal lettore.
Nota che da Clojure 1.7 in poi, puoi anche usare i trasduttori per leggere file di testo.
Numero 3: come scrivere in un nuovo file.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt")]
(.write wrtr "Line to be written"))
Ancora una volta, si with-open
prende cura che BufferedWriter
sia chiuso alla fine del corpo. Writer costringe una stringa in un BufferedWriter
, che usi usare tramite java interop:(.write wrtr "something").
Puoi anche usare spit
l'opposto di slurp
:
(spit "/tmp/test.txt" "Line to be written")
Numero 4: aggiungi una riga a un file esistente.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt" :append true)]
(.write wrtr "Line to be appended"))
Come sopra, ma ora con l'opzione Aggiungi.
O ancora con spit
, l'opposto di slurp
:
(spit "/tmp/test.txt" "Line to be written" :append true)
PS: Per essere più esplicito sul fatto che stai leggendo e scrivendo su un File e non qualcos'altro, potresti prima creare un oggetto File e poi forzarlo in un BufferedReader
o Writer:
(reader (file "/tmp/test.txt"))
;; or
(writer (file "tmp/test.txt"))
La funzione file è anche in clojure.java.io.
PS2: A volte è utile poter vedere qual è la directory corrente (quindi "."). Puoi ottenere il percorso assoluto in due modi:
(System/getProperty "user.dir")
o
(-> (java.io.File. ".") .getAbsolutePath)
(with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))
produce IOException Stream closed
invece di una raccolta di linee. Cosa fare? Sto ottenendo buoni risultati con la risposta di @satyagraha, però.
(with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
doseq
ritorni nil
possono portare a tempi tristi senza valore.
Se il file si adatta alla memoria, puoi leggerlo e scriverlo con slurp e sputo:
(def s (slurp "filename.txt"))
(s ora contiene il contenuto di un file come stringa)
(spit "newfile.txt" s)
Ciò crea newfile.txt se non viene chiuso e scrive il contenuto del file. Se vuoi aggiungere al file puoi farlo
(spit "filename.txt" s :append true)
Per leggere o scrivere un file in modo lineare, utilizzare il lettore e lo scrittore di Java. Sono racchiusi nello spazio dei nomi 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!"
Si noti che la differenza tra slurp / spit e gli esempi di lettore / scrittore è che il file rimane aperto (nelle istruzioni let) in quest'ultimo e la lettura e la scrittura sono memorizzate, quindi più efficienti quando si leggono / scrivono ripetutamente da / scrivere su un file.
Ecco ulteriori informazioni: slurp spit clojure.java.io Java's BufferedReader Java's Writer
Per quanto riguarda la domanda 2, a volte si desidera che il flusso di linee venga restituito come oggetto di prima classe. Per ottenere questo come una sequenza pigra e avere ancora il file chiuso automaticamente su EOF, ho usato questa funzione:
(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
sia Clojure ideomatico. Il tuo read-next-line
, per quanto ho capito, è visibile al di fuori della tua read-lines
funzione. Potresti aver usato un (let [read-next-line (fn [] ...))
invece.
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Per leggere un file riga per riga non è più necessario ricorrere all'interoperabilità:
(->> "data.csv"
io/resource
io/reader
line-seq
(drop 1))
Ciò presuppone che il file di dati sia conservato nella directory delle risorse e che la prima riga sia l'informazione dell'intestazione che può essere scartata.