Trong Clojure 1.3, Cách đọc và viết tệp


163

Tôi muốn biết cách "khuyến nghị" đọc và viết một tệp trong clojure 1.3.

  1. Cách đọc toàn bộ tập tin
  2. Cách đọc từng dòng tệp
  3. Cách viết một tập tin mới
  4. Cách thêm một dòng vào một tệp hiện có

2
Kết quả đầu tiên từ google: lethain.com/reading-file-in-clojure
jcubic

8
Kết quả này là từ năm 2009, một số điều đã được thay đổi gần đây.
Serge

11
Thật. Câu hỏi StackOverflow này hiện là kết quả đầu tiên trên Google.
mydoghasworms

Câu trả lời:


273

Giả sử chúng ta chỉ làm các tệp văn bản ở đây và không phải là một số thứ nhị phân điên rồ.

Số 1: cách đọc toàn bộ tập tin vào bộ nhớ.

(slurp "/tmp/test.txt")

Không được đề xuất khi nó là một tập tin thực sự lớn.

Số 2: cách đọc một dòng tệp theo dòng.

(use 'clojure.java.io)
(with-open [rdr (reader "/tmp/test.txt")]
  (doseq [line (line-seq rdr)]
    (println line)))

Các with-openvĩ mô chăm sóc mà người đọc được đóng cửa vào cuối của cơ thể. Hàm đọc buộc một chuỗi (nó cũng có thể tạo một URL, v.v.) thành một BufferedReader. line-seqcung cấp một seq lười biếng. Yêu cầu yếu tố tiếp theo của seq lười biếng dẫn đến một dòng được đọc từ người đọc.

Lưu ý rằng từ Clojure 1.7 trở đi, bạn cũng có thể sử dụng các bộ chuyển đổi để đọc tệp văn bản.

Số 3: làm thế nào để ghi vào một tập tin mới.

(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt")]
  (.write wrtr "Line to be written"))

Một lần nữa, hãy with-openquan tâm rằng BufferedWriterđóng cửa ở cuối cơ thể. Nhà văn đã ép buộc một chuỗi thành một BufferedWriter, mà bạn sử dụng thông qua java interop:(.write wrtr "something").

Bạn cũng có thể sử dụng spit, ngược lại slurp:

(spit "/tmp/test.txt" "Line to be written")

Số 4: nối một dòng vào một tệp hiện có.

(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt" :append true)]
  (.write wrtr "Line to be appended"))

Tương tự như trên, nhưng bây giờ với tùy chọn chắp thêm.

Hoặc một lần nữa với spit, ngược lại với slurp:

(spit "/tmp/test.txt" "Line to be written" :append true)

PS: Để rõ ràng hơn về thực tế là bạn đang đọc và ghi vào Tệp chứ không phải thứ gì khác, trước tiên bạn có thể tạo một đối tượng Tệp và sau đó ép buộc nó thành một BufferedReaderhoặc Trình ghi:

(reader (file "/tmp/test.txt"))
;; or
(writer (file "tmp/test.txt"))

Hàm tập tin cũng có trong clojure.java.io.

PS2: Đôi khi thật tiện dụng để có thể xem thư mục hiện tại (vì vậy ".") Là gì. Bạn có thể có được đường dẫn tuyệt đối theo hai cách:

(System/getProperty "user.dir") 

hoặc là

(-> (java.io.File. ".") .getAbsolutePath)

1
Cảm ơn bạn rất rất nhiều cho câu trả lời chi tiết của bạn. Tôi rất vui khi biết cách được đề xuất của Tệp IO (tệp văn bản) trong 1.3. Dường như đã có một số thư viện về File IO (clojure.contrb.io, clojure.contrib.duck-stream và một số ví dụ trực tiếp sử dụng Java BufferedReader FileInputStream InputStreamReader) khiến tôi bối rối hơn. Hơn nữa, có rất ít thông tin về Clojure 1.3 đặc biệt là tiếng Nhật (ngôn ngữ tự nhiên của tôi) Cảm ơn bạn.
jolly-san

Xin chào jolly-san, tnx vì đã chấp nhận câu trả lời của tôi! Đối với thông tin của bạn, clojure.contrib.duck-stream hiện không được chấp nhận. Điều này có thể thêm vào sự nhầm lẫn.
Michiel Borkent

Đó là, (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))sản lượng IOException Stream closedthay vì một bộ sưu tập các dòng. Phải làm sao Mặc dù nhận được kết quả tốt với câu trả lời của @satyagraha.
0dB

4
Điều này phải làm với sự lười biếng. Khi bạn sử dụng kết quả của dòng-seq bên ngoài mở, điều này xảy ra khi bạn in kết quả của nó sang REPL, thì đầu đọc đã bị đóng. Một giải pháp là bọc đường seq bên trong một doall, điều này buộc phải đánh giá ngay lập tức. (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent

Đối với những người mới bắt đầu thực tế như tôi, lưu ý rằng doseqlợi nhuận nilcó thể dẫn đến thời gian giá trị buồn-không trả lại.
Ngày

33

Nếu tập tin vừa với bộ nhớ, bạn có thể đọc và ghi nó với tiếng lách cách và nhổ:

(def s (slurp "filename.txt"))

(s hiện chứa nội dung của tệp dưới dạng chuỗi)

(spit "newfile.txt" s)

Điều này tạo newfile.txt nếu nó không thoát và ghi nội dung tệp. Nếu bạn muốn thêm vào tập tin, bạn có thể làm

(spit "filename.txt" s :append true)

Để đọc hoặc viết một tệp theo chiều dọc, bạn sẽ sử dụng trình đọc và ghi của Java. Chúng được gói trong không gian tên 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!"

Lưu ý rằng sự khác biệt giữa slurp / spit và các ví dụ về trình đọc / ghi là tệp vẫn mở (trong các câu lệnh let) ở phần sau và việc đọc và viết được đệm, do đó hiệu quả hơn khi đọc / ghi nhiều lần vào tệp.

Dưới đây là thông tin thêm: slurp nhổ clojure.java.io Trình soạn thảo Java của BufferedReader Java


1
Cảm ơn Paul. Tôi có thể tìm hiểu thêm bằng mã của bạn và nhận xét của bạn rõ ràng trong điểm tập trung vào trả lời câu hỏi của tôi. Cảm ơn rât nhiều.
jolly-san

Cảm ơn bạn đã thêm thông tin về các phương pháp cấp thấp hơn một chút không được đưa ra trong câu trả lời (xuất sắc) của Michiel Borkent về các phương pháp tốt nhất cho các trường hợp điển hình.
Sao Hỏa

@Mars Cảm ơn. Thật ra tôi đã trả lời câu hỏi này trước, nhưng câu trả lời của Michiel có nhiều cấu trúc hơn và điều đó dường như rất phổ biến.
Paul

Anh ấy làm một công việc tốt với các trường hợp thông thường, nhưng bạn cung cấp thông tin khác. Đó là lý do tại sao SE tốt cho phép nhiều câu trả lời.
Sao Hỏa

6

Về câu hỏi 2, đôi khi người ta muốn dòng dòng được trả về như một đối tượng hạng nhất. Để có được điều này như một chuỗi lười biếng và vẫn đóng tệp tự động trên EOF, tôi đã sử dụng chức năng này:

(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
Tôi không nghĩ làm tổ defnlà Clojure ý thức hệ. Theo read-next-linenhư tôi hiểu, bạn có thể nhìn thấy bên ngoài read-lineschức năng của bạn . Bạn có thể đã sử dụng một (let [read-next-line (fn [] ...))thay thế.
kristianlm

Tôi nghĩ rằng câu trả lời của bạn sẽ tốt hơn một chút nếu nó trả về hàm được tạo, (đóng trên trình đọc mở) thay vì ràng buộc toàn cầu.
Phường

1

Đây là cách đọc toàn bộ tập tin.

Nếu tệp nằm trong thư mục tài nguyên, bạn có thể làm điều này:

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

nhớ yêu cầu / sử dụng clojure.java.io.


0
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)

0

Để đọc một dòng tệp theo từng dòng, bạn không còn cần phải dùng đến interop:

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

Điều này giả định rằng tệp dữ liệu của bạn được lưu trong thư mục tài nguyên và dòng đầu tiên là thông tin tiêu đề có thể bị loại bỏ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.