【问题标题】:In Clojure 1.3, How to read and write a file在 Clojure 1.3 中,如何读写文件
【发布时间】:2011-12-07 02:24:04
【问题描述】:

我想知道在 clojure 1.3 中读写文件的“推荐”方式。

  1. 如何读取整个文件
  2. 如何逐行读取文件
  3. 如何编写新文件
  4. 如何在现有文件中添加一行

【问题讨论】:

  • google 的第一个结果:lethain.com/reading-file-in-clojure
  • 这个结果是 2009 年的,最近发生了一些变化。
  • 确实如此。这个 StackOverflow 问题现在是 Google 上的第一个结果。

标签: file clojure io


【解决方案1】:

这是读取整个文件的方法。

如果文件在资源目录中,你可以这样做:

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

记得要求/使用clojure.java.io

【讨论】:

    【解决方案2】:

    要逐行读取文件,您不再需要求助于互操作:

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

    这假定您的数据文件保存在资源目录中,并且第一行是可以丢弃的标头信息。

    【讨论】:

      【解决方案3】:

      假设我们在这里只处理文本文件而不是一些疯狂的二进制文件。

      第 1 点:如何将整个文件读入内存。

      (slurp "/tmp/test.txt")
      

      当文件非常大时不推荐。

      第二个:如何逐行读取文件。

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

      with-open 宏注意阅读器在正文的末尾是关闭的。 reader 函数将字符串(它也可以执行 URL 等)强制转换为 BufferedReaderline-seq 提供惰性序列。要求惰性序列的下一个元素会导致从阅读器读取一行。

      请注意,从 Clojure 1.7 开始,您还可以使用 transducers 来读取文本文件。

      第 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:为了更明确地说明您正在读取和写入 File 而不是其他内容,您可以先创建一个 File 对象,然后将其强制转换为 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.3中File IO(文本文件)的推荐方式。似乎有一些关于文件 IO 的库(clojure.contrb.io、clojure.contrib.duck-streams 和一些直接使用 Java BufferedReader FileInputStream InputStreamReader 的示例)让我更加困惑。此外,关于 Clojure 1.3 的信息很少,尤其是日语(我的自然语言)谢谢。
      • 嗨 jolly-san,tnx 接受我的回答!供您参考,clojure.contrib.duck-streams 现在已弃用。这可能会增加混乱。
      • 也就是说,(with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr)) 产生 IOException Stream closed 而不是行的集合。该怎么办?不过,@satyagraha 的回答效果很好。
      • 这与懒惰有关。当您在 with-open 之外使用 line-seq 的结果时,当您将其结果打印到 REPL 时会发生这种情况,那么阅读器已经关闭。一个解决方案是将 line-seq 包装在一个 doall 中,这会立即强制评估。 (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
      • 对于像我这样的实际初学者,请注意doseq 返回nil,这可能会导致悲伤无返回值时间。
      【解决方案4】:
      (require '[clojure.java.io :as io])
      (io/copy (io/file "/etc/passwd") \*out*\)
      

      【讨论】:

        【解决方案5】:

        关于问题 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 是理想的 Clojure。据我了解,您的 read-next-line 在您的 read-lines 函数之外可见。您可能改用了(let [read-next-line (fn [] ...))
        • 我认为如果它返回创建的函数(关闭打开的阅读器)而不是全局绑定,您的答案会更好一些。
        【解决方案6】:

        如果文件适合内存,您可以使用 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 和 reader/writer 示例之间的区别在于后者中的文件保持打开状态(在 let 语句中)并且读取和写入被缓冲,因此在重复读取/写入时效率更高一个文件。

        这里有更多信息:slurpspit clojure.java.io Java's BufferedReader Java's Writer

        【讨论】:

        • 谢谢保罗。我可以通过您的代码和您的 cmets 了解更多信息,这些代码和 cmets 在重点回答我的问题时很清楚。非常感谢。
        • 感谢您添加 Michiel Borkent 关于典型案例最佳方法的(优秀)答案中未提供的稍微低级方法的信息。
        • @Mars 谢谢。其实我确实先回答了这个问题,但是 Michiel 的回答结构更丰富,而且似乎很受欢迎。
        • 他在通常情况下做得很好,但您提供了其他信息。这就是为什么 SE 允许多个答案是件好事。
        猜你喜欢
        • 1970-01-01
        • 2012-01-23
        • 2017-09-02
        • 2012-04-25
        • 1970-01-01
        • 2016-08-10
        • 1970-01-01
        • 2014-06-29
        • 2019-06-04
        相关资源
        最近更新 更多