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-openmacro 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-seqoffre 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-openprende cura che BufferedWritersia chiuso alla fine del corpo. Writer costringe una stringa in un BufferedWriter, che usi usare tramite java interop:(.write wrtr "something").
Puoi anche usare spitl'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 BufferedReadero 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 closedinvece 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)))
doseqritorni nilpossono 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)))
defnsia Clojure ideomatico. Il tuo read-next-line, per quanto ho capito, è visibile al di fuori della tua read-linesfunzione. 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.