У 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 тощо) в a BufferedReader. line-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 примушує рядок до a BufferedWriter, який ви використовуєте через java interop:(.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
Дуже дякую за детальну відповідь. Я радий ознайомитись з рекомендованим способом File IO (текстовий файл) у 1.3. Здається, були деякі бібліотеки про File IO (clojure.contrb.io, clojure.contrib.duck-потоки та деякі приклади безпосередньо за допомогою Java BufferedReader FileInputStream InputStreamReader), що зробило мене більш заплутаним. Крім того, мало інформації про Clojure 1.3, особливо японською мовою (моя натуральна мова). Дякую.
веселий сан

Привіт веселий сан, tnx за прийняття моєї відповіді! Для вашої інформації Clojure.contrib.duck-потоки тепер застарілі. Це, можливо, додає плутанини.
Міхель Боркент

Тобто (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))врожайність IOException Stream closedзамість колекції ліній. Що робити? Хоча я отримую хороші результати з відповіддю @satyagraha.
0dB

4
Це стосується лінивості. Якщо ви використовуєте результат рядкової послідовності за межами з-відкритим, що відбувається, коли ви друкуєте його результат на REPL, тоді зчитувач вже закритий. Рішення полягає в тому, щоб загорнути послідовно-рядовий рядок всередині штриха, що змушує негайно оцінювати. (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 та прикладами для читання / запису полягає в тому, що файл залишається відкритим (у випущених висловлюваннях) в останньому, а читання і запис буферизовано, таким чином, більш ефективним при багаторазовому читанні з / запису у файл.

Ось додаткова інформація: slurp spit clojure.java.io Java's BufferedReader Writer Java


1
Дякую, Павло. Я міг би дізнатися більше за вашими кодами та вашими коментарями, які зрозумілі в моменті, присвяченому відповіді на моє запитання. Велике спасибі.
веселий сан

Дякуємо, що додали інформацію про методи нижчого рівня, які не наведені у відповіді Міхеля Боркента про найкращі методи для типових випадків.
Марс

@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є ідеальним клоджуром. Ваше 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

Щоб прочитати файл за рядком, вам більше не потрібно вдаватися до interop:

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

Це передбачає, що ваш файл даних зберігається в каталозі ресурсів і що перший рядок - це інформація заголовка, яку можна відкинути.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.