W Clojure 1.3, jak czytać i pisać plik

163

Chciałbym poznać „zalecany” sposób czytania i zapisywania pliku w clojure 1.3.

  1. Jak czytać cały plik
  2. Jak czytać plik wiersz po wierszu
  3. Jak napisać nowy plik
  4. Jak dodać linię do istniejącego pliku
jolly-san
źródło
2
Pierwszy wynik z google: lethain.com/reading-file-in-clojure
jcubic
8
To wynik z 2009 roku, ostatnio pewne rzeczy uległy zmianie.
Sergey
11
W rzeczy samej. To pytanie StackOverflow jest teraz pierwszym wynikiem w Google.
mydoghasworms

Odpowiedzi:

273

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-openMakro 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-seqdostarcza 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-openaby BufferedWriterna 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ć spitodwrotnoś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 spitprzeciwień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 BufferedReaderlub 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)
Michiel Borkent
źródło
1
Bardzo dziękuję za szczegółową odpowiedź. Cieszę się, że mogłem poznać zalecany sposób File IO (plik tekstowy) w 1.3. Wydaje się, że istniały biblioteki związane z File IO (clojure.contrb.io, clojure.contrib.duck-streams i kilka przykładów bezpośrednio używających Java BufferedReader FileInputStream InputStreamReader), co sprawiło, że byłem bardziej zagmatwany. Ponadto niewiele jest informacji o Clojure 1.3, zwłaszcza w języku japońskim (mój język naturalny). Dziękuję.
jolly-san
Cześć Jolly-san, tnx za przyjęcie mojej odpowiedzi! Dla Twojej informacji clojure.contrib.duck-streams jest teraz przestarzała. To prawdopodobnie pogłębia zamieszanie.
Michiel Borkent,
Oznacza to, że (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))daje plony IOException Stream closedzamiast zbioru linii. Co robić? Jednak otrzymuję dobre wyniki z odpowiedzią @satyagraha.
0dB
4
Ma to związek z lenistwem. Kiedy używasz wyniku line-seq poza with-open, co ma miejsce, gdy drukujesz jego wynik do REPL, to czytnik jest już zamknięty. Rozwiązaniem jest zawijanie ciągu linii wewnątrz doall, co wymusza natychmiastową ocenę. (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent
Dla początkujących, takich jak ja, zwróć uwagę, że doseqzwroty nilmogą prowadzić do smutnych czasów bez zwracania wartości.
października
33

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

Paweł
źródło
1
Dziękuję Paul. Mógłbym dowiedzieć się więcej z twoich kodów i twoich komentarzy, które są jasne w punkcie skupiającym się na odpowiedzi na moje pytanie. Dziękuję Ci bardzo.
jolly-san
Dziękuję za dodanie informacji o metodach nieco niższego poziomu, których nie podano w odpowiedzi Michiela Borkenta na temat najlepszych metod w typowych przypadkach.
Mars
@Mars Thanks. Właściwie to najpierw odpowiedziałem na to pytanie, ale odpowiedź Michiela ma większą strukturę i wydaje się być bardzo popularna.
Paul
Wykonuje dobrą robotę w zwykłych przypadkach, ale podałeś inne informacje. Dlatego dobrze, że SE pozwala na wiele odpowiedzi.
Mars
6

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)))
satyagraha
źródło
7
Nie sądzę, że zagnieżdżanie się defnjest ideomatyczną Clojure. Twój read-next-line, o ile rozumiem, jest widoczny poza twoją read-linesfunkcją. Mogłeś (let [read-next-line (fn [] ...))zamiast tego użyć .
kristianlm
Myślę, że twoja odpowiedź byłaby nieco lepsza, gdyby zwróciła utworzoną funkcję (zamykając otwarty czytnik), a nie globalnie wiążącą.
Oddział
1

Oto jak odczytać cały plik.

Jeśli plik znajduje się w katalogu zasobów, możesz to zrobić:

(let [file-content-str (slurp (clojure.java.io/resource "public/myfile.txt")])

pamiętaj, aby wymagać / używać clojure.java.io.

joshua
źródło
0
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Dima Fomin
źródło
0

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ć.

Chris Murphy
źródło