维护多个打开的文件进行写入(Clojure)

时间:2012-01-05 11:13:10

标签: clojure io

我需要编写一个函数,根据字段的值将记录拆分为单独的文件。例如。给出输入:

[
  ["Paul" "Smith" 35]
  ["Jason" "Nielsen" 39]
  ["Charles" "Brown" 22]
  ]

我们最终得到的文件"Paul"包含"Paul Smith 35",文件"Jason",包含"Jason Nielsen 39"等。

我事先不知道这些名字,所以我需要保留作者的参考资料,这样我才能最终关闭它们。

我能想到的最好的就是使用引用来保留作者,就像这样:

(defn write-split [records]
(let [out-dir (io/file "/tmp/test/")
      open-files (ref {})]
  (try
    (.mkdirs out-dir)
    (dorun
      (for [[fst lst age :as rec] records]
        (binding [*out* (or
                          (@open-files fst)
                          (dosync
                            (alter open-files assoc fst (io/writer (str out-dir "/" fst)))
                            (@open-files fst)))]
          (println (apply str (interpose " " rec))))))
    (finally (dorun (map #(.close %) (vals @open-files)))))))

这很有效,但是感觉很糟糕,而且更重要的是,尽管我只有五个输出文件,但它们在一开始就打开了。似乎某种东西被保留了......

有人能想到更具功能性和类似Clojure的解决方案吗?

编辑:输入很大。可能是千兆字节的数据,因此内存效率的重要性,以及每次写入后不愿关闭文件。

4 个答案:

答案 0 :(得分:3)

(use '[clojure.string :only (join)])

(defn write-records! [records]
  (let [writers (atom {})]
    (try 
      (doseq [[filename :as record] records]
        (let [w (or (get @writers
                         filename)
                    (get (swap! writers assoc filename (writer filename)) filename))]
          (.write w (str (join " " record) "\n"))))
      (finally (dorun (map #(.close (second %)) @writers))
               (reset! writers {})))))

答案 1 :(得分:2)

我想知道你的堆耗尽问题是否与for中绑定的使用有某种关系。看起来您的代码需要为每条记录添加新的绑定,并且可能保留旧的绑定。 (我可能完全错了,clojure绑定对我来说是一种黑暗的艺术)。

您可能会考虑让主记录排序代码将数据放入队列(每个逻辑文件可能有一个)。然后使用来自java Executor库的东西从队列中拉出一些“worker”(可能是关闭相应 out 绑定的编写器函数)。 (这个问题:"Sleeping a thread inside an ExecutorService (Java/Clojure)"可能会提供一些提示。)

您仍然需要优雅地关闭工作人员并以某种方式关闭文件。 (另一个问题"Clojure agents consuming from a queue"可能暗示一种方法。)

祝你好运!必须将无限数据上的序列抽象与文件系统不可避免的必要状态联系起来并不是微不足道的(但希望在Clojure中仍然比其他语言更简单)。

答案 2 :(得分:0)

with-open可以为您处理文件的关闭。

(ns sandbox.core
  (:require [clojure.java.io :as io]))

(def data [["Paul" "Smith" 35]
           ["Jason" "Nielsen" 39]
           ["Charles" "Brown" 22]])

(doseq [record data]
  (with-open [w (io/writer (first record))]
    (binding [*out* w]
      (apply println record))))

根据您的编辑,出于性能原因,您不希望始终打开和关闭文件。一种方法是将写入器保持在高速缓存中。以下方法使用core.memoize来记忆get-writer函数。写完所有记录后,缓存的编写器将关闭。

(defn write-data [data]
  (let [get-writer (memoize/memo #(io/writer % :append true))]
    (try
      (doseq [record data]
        (let [w (get-writer (first record))]
          (binding [*out* w]
            (apply println record))))
      (finally
       (dorun (map  #(.close %)
                    (vals (memoize/snapshot get-writer))))))))

答案 3 :(得分:0)

(use '[clojure.contrib.string :only [join]])

(def vecs [["Paul" "Smith" 35]["Jason" "Nielsen" 39]["Charles" "Brown" 22]]) 

(defn write-files [v] 
  (doseq [i v]
     (spit (i 0) ; the (0 1) gets the elem in the index 0 of the vec
            (join " " i))))

(write-files vecs)

这应该有效。