莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          Clojure世界:文件IO

          Posted on 2012-02-16 22:38 dennis 閱讀(7103) 評論(3)  編輯  收藏 所屬分類: Clojure
              文件讀寫是日常編程中最經常使用的操作之一。這篇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)



              這個例子基本上說明了大多數常見的操作。但是有些問題需要解釋下。

              首先,通過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))

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

             用法簡單明了,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> ⋯⋯ )

              上面的介紹已經足以讓你對付大多數需求了。接下來,介紹下幾個開源庫。首先是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")

          ⋯⋯

              更多介紹請看它的源碼。讀寫二進制文件也是一個很常見的需求,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)
              相當直觀和給力吧。Gloss是一個更強大的DSL庫,非常適合做網絡通訊的協議處理。這里就不多做介紹了,你可以自己看它的例子和文檔。
             
              轉載請注明出處:http://www.aygfsteel.com/killme2008/archive/2012/02/16/370144.html
             

          評論

          # re: Clojure世界:文件IO  回復  更多評論   

          2012-02-17 09:40 by tb
          很強大啊

          # re: Clojure世界:文件IO  回復  更多評論   

          2013-07-05 01:13 by pimgeek
          感謝分享, 關于 slurp 和 spit 的文件讀寫操作例子, 對我非常有幫助!

          # re: Clojure世界:文件IO  回復  更多評論   

          2014-05-27 11:42 by markxueyuan
          感覺這是中文世界介紹clojure最強的博客了!
          主站蜘蛛池模板: 南宁市| 筠连县| 平舆县| 大余县| 林甸县| 娄烦县| 内黄县| 大名县| 黑水县| 韩城市| 米泉市| 甘德县| 昭苏县| 武川县| 社会| 临江市| 孙吴县| 宿迁市| 晋州市| 峨山| 琼海市| 新乡县| 霍林郭勒市| 炉霍县| 漳浦县| 晋中市| 昭平县| 集贤县| 玛多县| 彭泽县| 旬邑县| 靖江市| 两当县| 临邑县| 延边| 中阳县| 龙南县| 凯里市| 秀山| 阿拉善左旗| 郑州市|