Clojure世界:文件IO
文件讀寫是日常編程中最經常使用的操作之一。這篇blog將大概介紹下Clojure裏對文件操作的常用類庫。首先介紹標準庫clojure.java.io,這是最經常用的IO庫,定義了常見的IO操作。
首先,直接看一個例子,可以熟悉下大多數常用的函數:
(ns io
(:use [clojure.java.io]))
;;file函數,獲取一個java.io.File對象
(def f (file "a.txt"))
;;拷貝文件使用copy
(copy f (file "b.txt"))
;;刪除文件,使用delete-file
(delete-file f)
;;更經常使用reader和writer
(def rdr (reader "b.txt" :encoding "utf-8"))
(def wtr (writer "c.txt" :append true))
;;copy可以接受多種類型的參數
(copy rdr wtr :buffer-size 4096)
;;關閉文件
(.close wtr)
(.close rdr)
(:use [clojure.java.io]))
;;file函數,獲取一個java.io.File對象
(def f (file "a.txt"))
;;拷貝文件使用copy
(copy f (file "b.txt"))
;;刪除文件,使用delete-file
(delete-file f)
;;更經常使用reader和writer
(def rdr (reader "b.txt" :encoding "utf-8"))
(def wtr (writer "c.txt" :append true))
;;copy可以接受多種類型的參數
(copy rdr wtr :buffer-size 4096)
;;關閉文件
(.close wtr)
(.close rdr)
這個例子基本上說明了大多數常見的操作。但是有些問題需要解釋下。
首先,通過file這個函數可以將各種類型的對象轉化為java.io.File對象,file可以接受String,URL,URI以及java.io.File本身作為參數,並返回java.io.File。有了File對象,你就可以調用java.io.File類中的各種方法,比如判斷文件是否存在:
(.exists (file "a.txt")) => true or false
其次,可以通過delete-file來刪除一個文件,它是調用File的delete方法來執行的,但是File.delete會返回一個布爾值告訴你成功還是失敗,如果返回false,那麼delete-file會拋出IO異常,如果你不想被這個異常打擾,可以讓它“保持安靜”:
(delete-file f true)
拷貝文件很簡單,使用copy搞定,copy也可以很“寬容”,也可以接受多種類型的參數並幫你自動轉換,input可以是InputStream, Reader, File, byte[] 或者String,而output可以是OutputStream, Writer或者File。是不是很給力?這都是通過Clojure的protocol和defmulti做到的。但是,copy並不幫你處理文件的關閉問題,假設你傳入的input是一個File,output也是一個File,copy會自動幫你打開InputStream和OutputStream並建立緩衝區做拷貝,但是它不會幫你關閉這兩個流,因此你要小心,如果你經常使用copy,這可能是個內存泄漏的隱患。
更常用的,我們一般都是用reader和writer函數來打開一個BufferedReader和BufferedWriter做讀寫,同樣reader和writer也可以接受多種多樣的參數類型,甚至包括Socket也可以。因為writer打開的通常是一個BufferedWriter,所以你如果用它寫文件,有時候發現write之後文件還是沒有內容,這是因為數據暫時寫到了緩衝區裏,沒有刷入到磁盤,可以明確地調用(.flush wtr)來強製寫入;或者在wtr關閉後係統幫你寫入。reader和writer還可以傳入一些配置項,如:encoding指定讀寫的字符串編碼,writer可以指定是否為append模式等。
Clojure並沒有提供關閉文件的函數或者宏,你簡單地調用close方法即可。clojure.java.io的設計很有原則,它不準備將java.io都封裝一遍,而是提供一些最常用方法的簡便wrapper供你使用。
剛才提到copy不會幫你關閉打開的文件流,但是我們可以利用with-open這個宏來自動幫你管理打開的流:
(with-open [rdr (reader "b.txt")
wtr (writer "c.txt")]
(copy rdr wtr))
wtr (writer "c.txt")]
(copy rdr wtr))
with-open宏會自動幫你關閉在binding vector裏打開的流,你不再需要自己調用close,也不用擔心不小心造成內存泄漏。因此我會推薦你盡量用reader和writer結合with-open來做文件操作,而不要使用file函數。file函數應該用在一些判斷是否存在,判斷文件是否為目錄等操作上。
在clojure.core裏,還有兩個最常用的函數slurp和spit,一個吃,一個吐,也就是slurp讀文件,而spit寫文件,他們類似Ruby的File裏的read和write,用來完整地讀或者寫文件:
(prn (slurp "c.txt"))
(spit "c.txt" "hello world")
(spit "c.txt" "hello world")
用法簡單明了,slurp將文件完整地讀出並返回字符串作為結果,它還接受:encoding參數來指定字符串編碼,你猜的沒錯,它就是用reader和with-open實現的。spit同樣很簡單,將content轉化為字符串寫入文件,也接受:encoding和:append參數。
深度優先遍曆目錄,可以使用file-seq,返回一個深度優先順序遍曆的目錄列表,這是一個LazySeq:
(user=> (file-seq (java.io.File. "."))
(#<File .> #<File ./.gitignore> #<File ./.lein-deps-sum> #<File ./b.txt> #<File ./c.txt> #<File ./classes> ⋯⋯ )
(#<File .> #<File ./.gitignore> #<File ./.lein-deps-sum> #<File ./b.txt> #<File ./c.txt> #<File ./classes> ⋯⋯ )
上麵的介紹已經足以讓你對付大多數需求了。接下來,介紹下幾個開源庫。首先是fs這個庫,它封裝了java.io.File類的大多數方法,讓你用起來很clojure way,很舒服,例如:
(exists? "a.txt")
(directory? "file")
(file? "file")
(name "/home/dennis/.inputrc")
(mkdir "/var/data")
(rename "a.txt" "b.txt")
(def tmp (temp-dir))
(glob #".*test.*")
(chmod 744 "a.txt")
⋯⋯
(directory? "file")
(file? "file")
(name "/home/dennis/.inputrc")
(mkdir "/var/data")
(rename "a.txt" "b.txt")
(def tmp (temp-dir))
(glob #".*test.*")
(chmod 744 "a.txt")
⋯⋯
更多介紹請看它的源碼。讀寫二進製文件也是一個很常見的需求,Clojure有幾個DSL庫幹這個事情,可以很直觀地定義二進製格式來encode/decode,比如byte-spec這個庫,看看它的例子:
defspec basic-spec
:a :int8
:b :int16
:c :int32
:d :float32
:e :float64
:f :string)
;; An object to serialize
(def foo {:a 10 :b 20 :c 40 :d 23.2 :e 23.2 :f "asddf"})
;; And serialize it to a byte array like this:
(spec-write-bytes basic-spec foo) ;; => [
bytes
]
;; reading in a byte array with the basic-spec format works like this:
(spec-read-bytes basic-spec bytes)
:a :int8
:b :int16
:c :int32
:d :float32
:e :float64
:f :string)
;; An object to serialize
(def foo {:a 10 :b 20 :c 40 :d 23.2 :e 23.2 :f "asddf"})
;; And serialize it to a byte array like this:
(spec-write-bytes basic-spec foo) ;; => [


;; reading in a byte array with the basic-spec format works like this:
(spec-read-bytes basic-spec bytes)
相當直觀和給力吧。Gloss是一個更強大的DSL庫,非常適合做網絡通訊的協議處理。這裏就不多做介紹了,你可以自己看它的例子和文檔。
文章轉自莊周夢蝶 ,原文發布時間2012-02-16
最後更新:2017-05-18 20:36:12