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