ใน Clojure 1.3 วิธีการอ่านและเขียนไฟล์


163

ฉันต้องการทราบวิธีการ "แนะนำ" ในการอ่านและเขียนไฟล์ใน Clojure 1.3

  1. วิธีอ่านไฟล์ทั้งหมด
  2. วิธีอ่านไฟล์ทีละบรรทัด
  3. วิธีเขียนไฟล์ใหม่
  4. วิธีเพิ่มบรรทัดลงในไฟล์ที่มีอยู่

2
ผลลัพธ์แรกจาก google: lethain.com/reading-file-in-clojure
jcubic

8
ผลลัพธ์นี้มาจากปี 2009 บางสิ่งมีการเปลี่ยนแปลงเมื่อเร็ว ๆ นี้
Sergey

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แมโครจะดูแลว่าผู้อ่านจะปิดท้ายของร่างกาย ฟังก์ชั่นอ่าน coerces สตริง (มันยังสามารถทำ URL ที่ ฯลฯ ) BufferedReaderเป็นline-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 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:เพื่อให้ชัดเจนยิ่งขึ้นเกี่ยวกับความจริงที่ว่าคุณกำลังอ่านและเขียนลงในไฟล์และไม่ใช่อย่างอื่นคุณสามารถสร้างวัตถุไฟล์ก่อนแล้วจึงบีบบังคับให้เป็นBufferedReaderหรือนักเขียน:

(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-streams และตัวอย่างบางส่วนโดยตรงโดยใช้ Java BufferedReader FileInputStream InputStreamReader) ซึ่งทำให้ฉันสับสนมากขึ้น นอกจากนี้ยังมีข้อมูลเล็กน้อยเกี่ยวกับ Clojure 1.3 โดยเฉพาะอย่างยิ่งในภาษาญี่ปุ่น (ภาษาธรรมชาติของฉัน) ขอบคุณ
jolly-san

สวัสดี jolly-san, tnx ที่ยอมรับคำตอบของฉัน! สำหรับข้อมูลของคุณ clojure.contrib.duck-stream ตอนนี้เลิกใช้แล้ว สิ่งนี้อาจเพิ่มความสับสน
Michiel Borkent

นั่นคือ(with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))อัตราผลตอบแทนIOException Stream closedแทนการสะสมของเส้น จะทำอย่างไร? ฉันได้รับผลลัพธ์ที่ดีพร้อมคำตอบโดย @satyagraha
0dB

4
สิ่งนี้เกี่ยวข้องกับความขี้เกียจ เมื่อคุณใช้ผลลัพธ์ของ line-seq ด้านนอกของ with-open ซึ่งเกิดขึ้นเมื่อคุณพิมพ์ผลลัพธ์ไปที่ REPL เครื่องอ่านจะปิดแล้ว วิธีแก้ไขคือห่อบรรทัด -qq ภายใน doall ซึ่งบังคับให้ประเมินผลทันที (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent

สำหรับผู้เริ่มต้นที่แท้จริงในฐานะฉันโปรดทราบว่าการdoseqส่งคืนnilซึ่งสามารถนำไปสู่เวลาที่น่าเศร้าไม่มีค่าตอบแทน
ต.ค.

33

หากไฟล์พอดีกับหน่วยความจำคุณสามารถอ่านและเขียนด้วย slurp และ spit:

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

(s ตอนนี้มีเนื้อหาของไฟล์เป็นสตริง)

(spit "newfile.txt" s)

สิ่งนี้จะสร้าง newfile.txt หากไม่ออกและเขียนเนื้อหาไฟล์ หากคุณต้องการผนวกไฟล์ที่คุณสามารถทำได้

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

หากต้องการอ่านหรือเขียนไฟล์ linewise คุณจะต้องใช้ตัวอ่านและตัวเขียนของ Java พวกเขาถูกห่อใน namespace 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 และตัวอย่าง reader / writer คือไฟล์ยังคงเปิดอยู่ (ในคำสั่ง let) ในภายหลังและการอ่านและการเขียนจะถูกบัฟเฟอร์ดังนั้นจึงมีประสิทธิภาพมากขึ้นเมื่ออ่านจาก / เขียนไปยังไฟล์ซ้ำ ๆ

นี่คือข้อมูลเพิ่มเติม: slurp spit clojure.java.io Java's BufferedReader Java's Writer


1
ขอบคุณพอล ฉันสามารถเรียนรู้เพิ่มเติมโดยรหัสของคุณและความคิดเห็นของคุณที่ชัดเจนในจุดที่มุ่งเน้นไปที่การตอบคำถามของฉัน ขอบคุณมาก.
jolly-san

ขอขอบคุณที่เพิ่มข้อมูลเกี่ยวกับวิธีการระดับล่างเล็กน้อยที่ไม่ได้ให้ไว้ในคำตอบ (ดีเลิศ) ของ Michiel Borkent เกี่ยวกับวิธีการที่ดีที่สุดสำหรับกรณีทั่วไป
ดาวอังคาร

@Mars ขอบคุณ ที่จริงฉันตอบคำถามนี้ก่อน แต่คำตอบของ Michiel มีโครงสร้างที่มากกว่าและดูเหมือนว่าจะเป็นที่นิยมมาก
พอล

เขาทำได้ดีในกรณีปกติ แต่คุณให้ข้อมูลอื่น นั่นเป็นเหตุผลว่าทำไมมันดีที่ 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

ฉันคิดว่าคำตอบของคุณจะดีขึ้นเล็กน้อยถ้ามันคืนค่าฟังก์ชั่นที่สร้างขึ้น (ปิดทับโปรแกรมอ่านที่เปิดอยู่) แทนที่จะเชื่อมโยงกับทั่วโลก
Ward

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))

นี่ถือว่าไฟล์ข้อมูลของคุณถูกเก็บไว้ในไดเรกทอรีทรัพยากรและบรรทัดแรกคือข้อมูลส่วนหัวที่สามารถละทิ้งได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.