Chciałbym poznać „zalecany” sposób czytania i zapisywania pliku w clojure 1.3.
- Jak czytać cały plik
- Jak czytać plik wiersz po wierszu
- Jak napisać nowy plik
- Jak dodać linię do istniejącego pliku
Chciałbym poznać „zalecany” sposób czytania i zapisywania pliku w clojure 1.3.
Odpowiedzi:
Zakładając, że robimy tutaj tylko pliki tekstowe, a nie jakieś szalone binarne rzeczy.
Numer 1: jak wczytać cały plik do pamięci.
(slurp "/tmp/test.txt")
Niezalecane, gdy jest to naprawdę duży plik.
Numer 2: jak czytać plik wiersz po wierszu.
(use 'clojure.java.io)
(with-open [rdr (reader "/tmp/test.txt")]
(doseq [line (line-seq rdr)]
(println line)))
with-open
Makro dba, że czytelnik jest zamknięta na końcu korpusu. Funkcja czytnika przekształca ciąg (może również wykonać adres URL itp.) Do pliku BufferedReader
. line-seq
dostarcza leniwy seq. Żądanie kolejnego elementu leniwego ciągu powoduje odczytanie wiersza przez czytelnika.
Zwróć uwagę, że począwszy od Clojure 1.7 możesz także używać przetworników do czytania plików tekstowych.
Numer 3: jak pisać do nowego pliku.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt")]
(.write wrtr "Line to be written"))
Ponownie, uważaj, with-open
aby BufferedWriter
na końcu korpusu był zamknięty. Writer przekształca ciąg w ciąg BufferedWriter
, którego używasz przez java interop:(.write wrtr "something").
Możesz także użyć spit
odwrotności slurp
:
(spit "/tmp/test.txt" "Line to be written")
Numer 4: dołącz linię do istniejącego pliku.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt" :append true)]
(.write wrtr "Line to be appended"))
To samo co powyżej, ale teraz z opcją dołączania.
Albo znowu z spit
przeciwieństwem slurp
:
(spit "/tmp/test.txt" "Line to be written" :append true)
PS: Aby być bardziej precyzyjnym co do faktu, że czytasz i zapisujesz do File, a nie do czegoś innego, możesz najpierw utworzyć obiekt File, a następnie przekształcić go w BufferedReader
lub Writer:
(reader (file "/tmp/test.txt"))
;; or
(writer (file "tmp/test.txt"))
Funkcja pliku znajduje się również w clojure.java.io.
PS2: Czasami warto zobaczyć, jaki jest bieżący katalog (czyli „.”). Możesz uzyskać ścieżkę absolutną na dwa sposoby:
(System/getProperty "user.dir")
lub
(-> (java.io.File. ".") .getAbsolutePath)
(with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))
daje plony IOException Stream closed
zamiast zbioru linii. Co robić? Jednak otrzymuję dobre wyniki z odpowiedzią @satyagraha.
(with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
doseq
zwroty nil
mogą prowadzić do smutnych czasów bez zwracania wartości.
Jeśli plik mieści się w pamięci, możesz go odczytać i zapisać za pomocą slurp i pluć:
(def s (slurp "filename.txt"))
(s zawiera teraz zawartość pliku jako ciąg)
(spit "newfile.txt" s)
Tworzy to newfile.txt, jeśli nie kończy pracy i zapisuje zawartość pliku. Jeśli chcesz dołączyć do pliku, możesz to zrobić
(spit "filename.txt" s :append true)
Aby czytać lub zapisywać plik liniowo, należy użyć czytnika i piszącego języka Java. Są one umieszczone w przestrzeni nazw 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!"
Zauważ, że różnica między slurp / spit a przykładami czytnika / pisarza polega na tym, że plik pozostaje otwarty (w instrukcjach let) w tym ostatnim, a odczyt i zapis jest buforowany, dzięki czemu jest bardziej wydajny, gdy wielokrotnie czyta z / zapisuje do pliku.
Tutaj jest więcej informacji: slurp spit clojure.java.io Java's BufferedReader Java's Writer
Odnośnie pytania 2, czasami chce się, aby strumień linii był zwracany jako obiekt pierwszej klasy. Aby uzyskać to jako leniwą sekwencję i nadal mieć plik automatycznie zamykany na EOF, użyłem tej funkcji:
(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
jest ideomatyczną Clojure. Twój read-next-line
, o ile rozumiem, jest widoczny poza twoją read-lines
funkcją. Mogłeś (let [read-next-line (fn [] ...))
zamiast tego użyć .
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Aby odczytać plik wiersz po wierszu, nie musisz już uciekać się do interopu:
(->> "data.csv"
io/resource
io/reader
line-seq
(drop 1))
Zakłada się, że plik danych jest przechowywany w katalogu zasobów, a pierwsza linia zawiera informacje nagłówkowe, które można odrzucić.