Clojure 1.3, Dosya okuma ve yazma


163

Ben clojure 1.3 bir dosya okuma ve yazma "önerilen" yolunu bilmek istiyorum.

  1. Tüm dosya nasıl okunur
  2. Dosya satır satır nasıl okunur
  3. Yeni bir dosya nasıl yazılır
  4. Varolan bir dosyaya satır nasıl eklenir

2
Google'dan ilk sonuç: lethain.com/reading-file-in-clojure
jcubic

8
Bu sonuç 2009'dan beri, son zamanlarda bazı şeyler değişti.
Sergey

11
Aslında. Bu StackOverflow sorusu artık Google'daki ilk sonuç.
mydoghasworms

Yanıtlar:


273

Burada sadece metin dosyaları yaptığımızı varsayarsak, çılgın ikili şeyler değil.

Sayı 1: bir dosyanın tamamını belleğe okuma.

(slurp "/tmp/test.txt")

Gerçekten büyük bir dosya olduğunda önerilmez.

Sayı 2: bir dosyayı satır satır okuma.

(use 'clojure.java.io)
(with-open [rdr (reader "/tmp/test.txt")]
  (doseq [line (line-seq rdr)]
    (println line)))

with-openMakro okuyucu gövdenin ucunda kapalı olduğunu ilgilenir. Okuyucu işlevi, bir dizeyi (ayrıca bir URL vb. De yapabilir) a BufferedReader. line-seqtembel bir seq sağlar. Tembel seq'in bir sonraki elemanını talep etmek, okuyucudan okunan bir satırla sonuçlanır.

Clojure 1.7'den itibaren metin dosyalarını okumak için dönüştürücüler de kullanabileceğinizi unutmayın .

Sayı 3: Yeni bir dosyaya nasıl yazılır.

(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt")]
  (.write wrtr "Line to be written"))

Yine, vücudun sonunda kapalı with-openolmasına dikkat eder BufferedWriter. Yazar, bir dizeyi BufferedWriterjava birlikte çalışma yoluyla kullandığınız bir dizeye zorlar :(.write wrtr "something").

Ayrıca kullanabilirsiniz spit, tam tersi slurp:

(spit "/tmp/test.txt" "Line to be written")

Sayı 4: varolan bir dosyaya satır ekleyin.

(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt" :append true)]
  (.write wrtr "Line to be appended"))

Yukarıdaki ile aynı, ancak şimdi ekleme seçeneği ile.

Veya yine spitbunun tersi slurp:

(spit "/tmp/test.txt" "Line to be written" :append true)

Not: Başka bir şey değil, bir Dosyaya okuduğunuz ve yazdığınız gerçeği hakkında daha açık olmak için, önce bir File nesnesi oluşturabilir ve daha sonra bunu bir BufferedReaderveya Yazar'a zorlayabilirsiniz:

(reader (file "/tmp/test.txt"))
;; or
(writer (file "tmp/test.txt"))

Dosya işlevi clojure.java.io dosyasında da bulunur.

PS2: Bazen geçerli dizinin (yani ".") Ne olduğunu görebilmek faydalı olabilir. Mutlak yolu iki şekilde elde edebilirsiniz:

(System/getProperty "user.dir") 

veya

(-> (java.io.File. ".") .getAbsolutePath)

1
Ayrıntılı cevabınız için çok teşekkür ederim. 1.3'te önerilen Dosya G / Ç (metin dosyası) yolunu tanımaktan memnuniyet duyuyorum. File IO hakkında bazı kütüphaneler var gibi görünüyor (clojure.contrb.io, clojure.contrib.duck-streams ve doğrudan Java BufferedReader FileInputStream InputStreamReader kullanan bazı örnekler). Üstelik Clojure 1.3 hakkında özellikle Japonca (benim doğal dilim) hakkında çok az bilgi var Teşekkürler.
jolly-san

Merhaba jolly-san, cevabımı kabul ettiğin için tnx! Bilginiz için clojure.contrib.duck-streams artık kullanımdan kaldırıldı. Bu muhtemelen karışıklığa katkıda bulunur.
Michiel Borkent

Yani, bir çizgi koleksiyonu yerine (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))verimler IOException Stream closed. Ne yapalım? Yine de @satyagraha cevabı ile iyi sonuçlar alıyorum.
0dB

4
Bu tembellik ile ilgili. Line-seq sonucunu with-open dışında kullandığınızda, sonucu REPL'e yazdırdığınızda gerçekleşir, ardından okuyucu zaten kapalıdır. Çözüm, line-seq'i değerlendirmeyi hemen zorlayan bir doall içine sarmaktır. (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent

Benim gibi yeni başlayanlar için, geri dönüş-değer-üzgün-değer sürelerine yol açabilecek doseqgetirilere dikkat edin nil.
Ekim

33

Dosya belleğe sığarsa, slurp ve spit ile okuyabilir ve yazabilirsiniz:

(def s (slurp "filename.txt"))

(s artık bir dosyanın içeriğini dize olarak içeriyor)

(spit "newfile.txt" s)

Bu, çıkmaz ve dosya içeriğini yazarsa newfile.txt dosyasını oluşturur. Eğer dosyaya eklemek istiyorsanız,

(spit "filename.txt" s :append true)

Bir dosyayı okumak veya yazmak için Java'nın okuyucusunu ve yazarını kullanırsınız. Bunlar clojure.java.io ad alanına sarılır:

(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!"

Slurp / spit ve okuyucu / yazar örnekleri arasındaki farkın, dosyanın ikincisinde açık kalmasına (let ifadelerinde) ve okuma ve yazmanın arabelleğe alındığına, bu nedenle bir dosyadan / dosyaya tekrar tekrar okunduğunda daha verimli olduğuna dikkat edin.

Daha fazla bilgi: slurp spit clojure.java.io Java'nın BufferedReader Java's Writer


1
Teşekkürler Paul. Soruma cevap vermeye odaklanan açık olan kodlarınız ve yorumlarınızla daha fazla bilgi edinebilirim. Çok teşekkür ederim.
jolly-san

Michiel Borkent'in tipik durumlar için en iyi yöntemler hakkındaki (mükemmel) cevabında verilmeyen biraz daha düşük seviyeli yöntemler hakkında bilgi eklediğiniz için teşekkür ederiz.
Mars

@Mars Teşekkürler. Aslında önce bu soruya cevap verdim, ancak Michiel'in cevabı daha fazla yapıya sahip ve bu çok popüler görünüyor.
Paul

Her zamanki vakalarda iyi bir iş çıkarıyor, ancak başka bilgiler verdiniz. Bu yüzden SE'nin birden fazla yanıta izin vermesi iyidir.
Mars

6

Soru 2 ile ilgili olarak, bazen satır akışının birinci sınıf bir nesne olarak döndürülmesi gerekir. Bunu tembel bir dizi olarak almak ve hala dosyayı EOF üzerinde otomatik olarak kapatmak için bu işlevi kullandım:

(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)))

7
Yuvalamanın defnideomatik Clojure olduğunu düşünmüyorum . Sizin read-next-line, bildiğim kadarıyla anlamak, dışında görünür olan read-linesişlevi. Bunun (let [read-next-line (fn [] ...))yerine bir tane kullanmış olabilirsiniz .
kristianlm

Küresel olarak bağlanmaktan ziyade, oluşturulan işlevi (açık okuyucuyu kapatarak) döndürürse cevabınızın biraz daha iyi olacağını düşünüyorum.
Ward

1

Dosyanın tamamı bu şekilde okunur.

Dosya kaynak dizinindeyse bunu yapabilirsiniz:

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

gerektirmeyi / kullanmayı unutmayın clojure.java.io.


0
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)

0

Bir dosyayı satır satır okumak için artık birlikte çalışmaya başvurmanız gerekmez:

(->> "data.csv"
      io/resource
      io/reader
      line-seq
      (drop 1))

Bu, veri dosyanızın kaynaklar dizininde tutulduğunu ve ilk satırın atılabilecek başlık bilgileri olduğunu varsayar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.