Мені хотілося б знати "рекомендований" спосіб читання та запису файлу в 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 тощо) в 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)
(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 та прикладами для читання / запису полягає в тому, що файл залишається відкритим (у випущених висловлюваннях) в останньому, а читання і запис буферизовано, таким чином, більш ефективним при багаторазовому читанні з / запису у файл.
Ось додаткова інформація: slurp spit clojure.java.io Java's BufferedReader Writer Java
Щодо питання 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
є ідеальним клоджуром. Ваше read-next-line
, наскільки я розумію, видно поза вашою read-lines
функцією. Можливо, ви скористалися (let [read-next-line (fn [] ...))
натомість.
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Щоб прочитати файл за рядком, вам більше не потрібно вдаватися до interop:
(->> "data.csv"
io/resource
io/reader
line-seq
(drop 1))
Це передбачає, що ваш файл даних зберігається в каталозі ресурсів і що перший рядок - це інформація заголовка, яку можна відкинути.