J'aimerais connaître la manière "recommandée" de lire et d'écrire un fichier dans clojure 1.3.
- Comment lire le fichier entier
- Comment lire un fichier ligne par ligne
- Comment écrire un nouveau fichier
- Comment ajouter une ligne à un fichier existant
J'aimerais connaître la manière "recommandée" de lire et d'écrire un fichier dans clojure 1.3.
Réponses:
En supposant que nous ne faisons que des fichiers texte ici et pas des trucs binaires fous.
Numéro 1: comment lire un fichier entier en mémoire.
(slurp "/tmp/test.txt")
Non recommandé lorsqu'il s'agit d'un très gros fichier.
Numéro 2: comment lire un fichier ligne par ligne.
(use 'clojure.java.io)
(with-open [rdr (reader "/tmp/test.txt")]
(doseq [line (line-seq rdr)]
(println line)))
La with-open
macro veille à ce que le lecteur soit fermé à l'extrémité du corps. La fonction de lecture contraint une chaîne (elle peut également faire une URL, etc.) dans un fichier BufferedReader
. line-seq
délivre un seq paresseux. Exiger l'élément suivant du paresseux seq entraîne la lecture d'une ligne par le lecteur.
Notez qu'à partir de Clojure 1.7, vous pouvez également utiliser des transducteurs pour lire des fichiers texte.
Numéro 3: comment écrire dans un nouveau fichier.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt")]
(.write wrtr "Line to be written"))
Encore une fois, with-open
veille à ce que le BufferedWriter
soit fermé à l'extrémité du corps. Writer contraint une chaîne en a BufferedWriter
, que vous utilisez via java interop:(.write wrtr "something").
Vous pouvez également utiliser spit
, l'opposé de slurp
:
(spit "/tmp/test.txt" "Line to be written")
Numéro 4: ajoutez une ligne à un fichier existant.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt" :append true)]
(.write wrtr "Line to be appended"))
Identique à ci-dessus, mais maintenant avec l'option d'ajout.
Ou encore avec spit
, l'opposé de slurp
:
(spit "/tmp/test.txt" "Line to be written" :append true)
PS: Pour être plus explicite sur le fait que vous lisez et écrivez dans un fichier et pas autre chose, vous pouvez d'abord créer un objet File, puis le forcer dans un BufferedReader
ou Writer:
(reader (file "/tmp/test.txt"))
;; or
(writer (file "tmp/test.txt"))
La fonction de fichier est également dans clojure.java.io.
PS2: Parfois, il est pratique de voir quel est le répertoire actuel (donc "."). Vous pouvez obtenir le chemin absolu de deux manières:
(System/getProperty "user.dir")
ou
(-> (java.io.File. ".") .getAbsolutePath)
(with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))
cède IOException Stream closed
au lieu d'une collection de lignes. Que faire? J'obtiens de bons résultats avec la réponse de @satyagraha, cependant.
(with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
doseq
retours nil
peuvent conduire à des temps tristes sans valeur de retour.
Si le fichier tient dans la mémoire, vous pouvez le lire et l'écrire avec slurp et spit:
(def s (slurp "filename.txt"))
(s contient maintenant le contenu d'un fichier sous forme de chaîne)
(spit "newfile.txt" s)
Cela crée newfile.txt s'il ne se ferme pas et écrit le contenu du fichier. Si vous souhaitez ajouter au fichier, vous pouvez faire
(spit "filename.txt" s :append true)
Pour lire ou écrire un fichier par ligne, vous utiliseriez le lecteur et l'écrivain Java. Ils sont enveloppés dans l'espace de noms 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!"
Notez que la différence entre slurp / spit et les exemples de lecture / écriture est que le fichier reste ouvert (dans les instructions let) dans ce dernier et que la lecture et l'écriture sont tamponnées, donc plus efficaces lors de la lecture / écriture répétée dans un fichier.
Voici plus d'informations: slurp spit clojure.java.io Java's BufferedReader Java's Writer
Concernant la question 2, on souhaite parfois que le flux de lignes soit renvoyé comme un objet de première classe. Pour obtenir cela comme une séquence paresseuse et que le fichier soit toujours fermé automatiquement sur EOF, j'ai utilisé cette fonction:
(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
soit un Clojure idéomatique. Votre read-next-line
, pour autant que je sache, est visible en dehors de votre read-lines
fonction. Vous avez peut-être utilisé un (let [read-next-line (fn [] ...))
.
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Pour lire un fichier ligne par ligne, vous n'avez plus besoin de recourir à l'interopérabilité:
(->> "data.csv"
io/resource
io/reader
line-seq
(drop 1))
Cela suppose que votre fichier de données est conservé dans le répertoire des ressources et que la première ligne contient des informations d'en-tête qui peuvent être supprimées.