Clojure 1.3では、ファイルを読み書きする方法


163

clojure 1.3でファイルを読み書きするための「推奨」方法を教えてください。

  1. ファイル全体を読み取る方法
  2. ファイルを1行ずつ読み取る方法
  3. 新しいファイルを書く方法
  4. 既存のファイルに行を追加する方法

2
googleからの最初の結果:lethain.com/reading-file-in-clojure
jcubic

8
この結果は2009年のもので、最近変更されたものがあります。
Sergey、

11
確かに。このStackOverflowの質問がGoogleでの最初の結果になりました。
mydoghasworms 2013

回答:


273

ここではテキストファイルのみを実行し、一部のクレイジーなバイナリファイルは実行していないと想定しています。

番号1:ファイル全体をメモリに読み込む方法。

(slurp "/tmp/test.txt")

非常に大きなファイルの場合はお勧めしません。

番号2:ファイルを1行ずつ読み取る方法。

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

with-openマクロは、リーダーが本体の端部が閉じていることを世話をします。リーダー関数は文字列(URLなども実行できます)をに変換し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-openBufferedWriter本体の端で閉じていることに注意してください。Writerは文字列をに変換します。これは、BufferedWriterJava相互運用機能を介して使用します。(.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"))

file関数もclojure.java.ioにあります。

PS2:ときどき、現在のディレクトリ(「。」)が何であるかを確認できると便利です。絶対パスを取得するには、次の2つの方法があります。

(System/getProperty "user.dir") 

または

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

1
細かい回答ありがとうございました。1.3で推奨されるファイルIO(テキストファイル)の方法を知ってよかったです。ファイルIOに関するいくつかのライブラリ(clojure.contrb.io、clojure.contrib.duck-streams、およびJavaのBufferedReader FileInputStream InputStreamReaderを直接使用するいくつかの例)があり、混乱を招いたようです。さらに、Clojure 1.3に関する情報はほとんどありません。特に日本語(私の自然言語)でありがとうございます。
jolly-san

こんにちはジョリーさん、私の答えを受け入れてくれたtnx!参考までに、clojure.contrib.duck-streamsは非推奨になりました。これはおそらく混乱を助長します。
Michiel Borkent、2011年

つまり、ラインのコレクションで(with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))IOException Stream closedなく、yields です。何をすべきか?ただし、@ satyagrahaの回答で良い結果が得られています。
0dB '27 / 10/27

4
これは怠惰と関係があります。結果をREPLに出力するときに発生する、with-openの外でline-seqの結果を使用する場合、リーダーはすでに閉じています。解決策は、doall内でline-seqをラップすることです。(with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent 2013年

私としての実際の初心者にとって、doseqリターンはnil悲しい無返還価値の時間につながる可能性があることに注意してください。
10月

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とreader / writerの例の違いは、後者ではファイルが(letステートメントで)開いたままであり、読み取りと書き込みがバッファリングされるため、ファイルの読み取りと書き込みを繰り返し行う場合により効率的です。

ここではより多くの情報がある:吸い込み clojure.java.io JavaのBufferedReaderの Javaのライター


1
ポールありがとう。私の質問への回答に焦点を当てている点で明確であるあなたのコードとコメントによって私はもっと学ぶことができました。どうもありがとうございました。
jolly-san

典型的なケースに最適な方法に関するMichiel Borkentの(優れた)回答に記載されていない、少し低レベルの方法に関する情報を追加していただきありがとうございます。
火曜日

@火星ありがとう。実際、私は最初にこの質問に答えましたが、ミシエルの答えはもっと構造的で、それは非常に人気があるようです。
ポール

彼は通常のケースで良い仕事をしていますが、あなたは他の情報を提供しました。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 2013年

グローバルにバインドするよりも、作成された関数(開いているリーダーを閉じる)が返された方が、答えが少し良くなると思います。
ウォード

1

これは、ファイル全体を読み取る方法です。

ファイルがリソースディレクトリにある場合、これを行うことができます。

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

require / useを忘れないでくださいclojure.java.io



0

ファイルを1行ずつ読み取るために、相互運用に頼る必要はありません。

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

これは、データファイルがリソースディレクトリに保持され、最初の行が破棄できるヘッダー情報であることを前提としています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.