Я хотел бы знать «рекомендуемый» способ чтения и записи файла в clojure 1.3.
- Как прочитать весь файл
- Как читать файл построчно
- Как написать новый файл
- Как добавить строку в существующий файл
Я хотел бы знать «рекомендуемый» способ чтения и записи файла в clojure 1.3.
Ответы:
Предполагая, что мы делаем только текстовые файлы здесь, а не какие-то сумасшедшие бинарные вещи.
Номер 1: как прочитать весь файл в память.
(slurp "/tmp/test.txt")
Не рекомендуется, когда это действительно большой файл.
Номер 2: как читать файл построчно.
(use 'clojure.java.io)
(with-open [rdr (reader "/tmp/test.txt")]
(doseq [line (line-seq rdr)]
(println line)))
with-open
Макрос заботится о том , что читатель закрыт в конце тела. Функция читателя принуждает строку (она также может сделать URL, и т.д.) в BufferedReader
. line-seq
поставляет ленивый seq. Требование следующего элемента ленивого seq приводит к строке, читаемой читателем.
Обратите внимание, что начиная с Clojure 1.7, вы также можете использовать преобразователи для чтения текстовых файлов.
Номер 3: как записать в новый файл.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt")]
(.write wrtr "Line to be written"))
Опять же, with-open
заботится о том, чтобы BufferedWriter
он был закрыт в конце тела. Writer приводит строку в BufferedWriter
, которую вы используете, используя взаимодействие с Java:(.write wrtr "something").
Вы также можете использовать spit
, противоположность slurp
:
(spit "/tmp/test.txt" "Line to be written")
Номер 4: добавить строку в существующий файл.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt" :append true)]
(.write wrtr "Line to be appended"))
То же, что и выше, но теперь с опцией добавления.
Или снова с spit
противоположностью slurp
:
(spit "/tmp/test.txt" "Line to be written" :append true)
PS: Чтобы быть более точным в том, что вы читаете и записываете в файл, а не во что-то еще, вы можете сначала создать объект File, а затем привести его к типу BufferedReader
или Writer:
(reader (file "/tmp/test.txt"))
;; or
(writer (file "tmp/test.txt"))
Файловая функция также находится в clojure.java.io.
PS2: Иногда удобно видеть, что представляет собой текущий каталог (поэтому "."). Вы можете получить абсолютный путь двумя способами:
(System/getProperty "user.dir")
или
(-> (java.io.File. ".") .getAbsolutePath)
(with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))
выдает IOException Stream closed
вместо набора строк. Что делать? Получаю хорошие результаты с ответом @satyagraha, хотя.
(with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
doseq
возвращаетсяnil
могут привести к временам грустного отсутствия возврата.
Если файл помещается в память, вы можете прочитать и записать его с помощью slurp и spit:
(def s (slurp "filename.txt"))
(s теперь содержит содержимое файла в виде строки)
(spit "newfile.txt" s)
Это создает newfile.txt, если он не завершает работу и записывает содержимое файла. Если вы хотите добавить в файл, вы можете сделать
(spit "filename.txt" s :append true)
Для чтения или записи файла вы также должны использовать программу чтения и записи Java. Они заключены в пространство имен 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!"
Обратите внимание, что разница между slurp / spit и примерами чтения / записи заключается в том, что файл остается открытым (в операторах let) в последнем, а чтение и запись буферизуются, таким образом, более эффективно при многократном чтении / записи в файл.
Вот дополнительная информация: slurp spit clojure.java.io Java BufferedReader Java's Writer
Что касается вопроса 2, иногда хочется, чтобы поток строк возвращался как первоклассный объект. Чтобы получить это как ленивую последовательность и при этом автоматически закрывать файл в EOF, я использовал эту функцию:
(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
является идеологическим Clojure. Ваш read-next-line
, насколько я понимаю, виден за пределами вашей read-lines
функции. Вы могли бы использовать (let [read-next-line (fn [] ...))
вместо этого.
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Чтобы читать файл построчно, вам больше не нужно прибегать к взаимодействию:
(->> "data.csv"
io/resource
io/reader
line-seq
(drop 1))
Это предполагает, что ваш файл данных хранится в каталоге ресурсов, а первая строка содержит информацию заголовка, которую можно отбросить.