В Clojure 1.3, Как читать и писать файл

163

Я хотел бы знать «рекомендуемый» способ чтения и записи файла в clojure 1.3.

  1. Как прочитать весь файл
  2. Как читать файл построчно
  3. Как написать новый файл
  4. Как добавить строку в существующий файл
весел-Сан -
источник
2
Первый результат от Google: lethain.com/reading-file-in-clojure
jcubic
8
Этот результат с 2009 года, некоторые вещи были изменены в последнее время.
Сергей
11
На самом деле. Этот вопрос StackOverflow теперь является первым результатом в Google.
mydoghasworms

Ответы:

273

Предполагая, что мы делаем только текстовые файлы здесь, а не какие-то сумасшедшие бинарные вещи.

Номер 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)
Михель Боркент
источник
1
Большое спасибо за ваш подробный ответ. Я рад узнать рекомендуемый способ ввода-вывода файла (текстовый файл) в 1.3. Кажется, было несколько библиотек о Файловом вводе-выводе (clojure.contrb.io, clojure.contrib.duck-streams и некоторые примеры, непосредственно использующие Java BufferedReader FileInputStream InputStreamReader), которые привели меня в замешательство. Более того, мало информации о Clojure 1.3, особенно на японском (мой естественный язык). Спасибо.
Веселый Сан
Привет, Джолли-сан, спасибо за ответ! Для вашей информации, clojure.contrib.duck-streams теперь устарела. Это, возможно, добавляет путаницы.
Михиль Боркент,
То есть (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))выдает IOException Stream closedвместо набора строк. Что делать? Получаю хорошие результаты с ответом @satyagraha, хотя.
0 дБ
4
Это связано с ленью. Когда вы используете результат line-seq вне with-open, что происходит, когда вы печатаете его результат в REPL, считыватель уже закрыт. Решение состоит в том, чтобы обернуть строку-последовательность в doall, что немедленно вызывает оценку. (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Михель Боркент
Для фактических начинающих, как я, обратите внимание, что doseqвозвращаетсяnil могут привести к временам грустного отсутствия возврата.
октября
33

Если файл помещается в память, вы можете прочитать и записать его с помощью 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

Павел
источник
1
Спасибо, Пол. Я мог бы узнать больше по вашим кодам и вашим комментариям, которые ясны в том, что касается ответа на мой вопрос. Большое спасибо.
Jolly-San
Спасибо за добавление информации о методах несколько более низкого уровня, не приведенных в ответе Михеля Боркента (отлично) о лучших методах для типичных случаев.
Марс
@Mars Спасибо. На самом деле я сначала ответил на этот вопрос, но у Михеля больше структуры, и это кажется очень популярным.
Пол
Он хорошо справляется с обычными делами, но вы предоставили другую информацию. Вот почему хорошо, что SE допускает несколько ответов.
Марс
6

Что касается вопроса 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)))
сатьяграху
источник
7
Я не думаю, что вложение defnявляется идеологическим Clojure. Ваш read-next-line, насколько я понимаю, виден за пределами вашей read-linesфункции. Вы могли бы использовать (let [read-next-line (fn [] ...))вместо этого.
kristianlm
Я думаю, что ваш ответ был бы немного лучше, если бы он возвращал созданную функцию (закрывающуюся при открытом ридере), а не глобальную привязку.
Уорд
1

Это как прочитать весь файл.

Если файл находится в каталоге ресурсов, вы можете сделать это:

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

не забудьте требовать / использовать clojure.java.io.

Джошуа
источник
0
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Дима Фомин
источник
0

Чтобы читать файл построчно, вам больше не нужно прибегать к взаимодействию:

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

Это предполагает, что ваш файл данных хранится в каталоге ресурсов, а первая строка содержит информацию заголовка, которую можно отбросить.

Крис Мерфи
источник