【问题标题】:Maintaining multiple open files for writing (Clojure)维护多个打开的文件以进行写入(Clojure)
【发布时间】:2012-01-05 11:13:09
【问题描述】:

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

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

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

我事先不知道名字,所以我需要保留作者的参考,以便我最终可以关闭它们。

我能想到的最好办法是使用 ref 来保留作者,如下所示:

(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 的解决方案?

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

【问题讨论】:

  • 记录数据太大了不能在内存中分组? (group-by first records) 然后为返回的映射中的每个新键打开和关闭一个文件。
  • 是的,它很大——数千兆字节的数据通过惰性序列传入。理想情况下是无限的。
  • 是否需要保持文件打开,或者需要打开的文件总数是有限的?大多数操作系统只允许打开一定数量的文件。
  • 有限,少量文件 - 最多 20 个。严格来说,它们不需要打开,但每次写入后关闭文件(与 with-open 宏一样)不是一个选项.

标签: clojure io


【解决方案1】:
(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 {})))))

【讨论】:

  • @ 符号在这里有什么作用?它似乎不太适合 Google 使用
【解决方案2】:

我想知道您的堆用完问题是否与在for 中使用绑定有关。看起来您的代码对每条记录都需要一个新的绑定,并且可能会保留旧的绑定。 (我可能完全错了,clojure 绑定对我来说是一门黑暗的艺术)。

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

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

祝你好运!必须将无限数据上的序列抽象与文件系统不可避免的命令式状态相结合并非易事(但希望在 Clojure 中比在其他语言中更简单)。

【讨论】:

    【解决方案3】:
    (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)
    

    这应该有效。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-08-22
        • 1970-01-01
        • 1970-01-01
        • 2013-08-15
        • 2021-03-31
        • 1970-01-01
        • 2019-06-25
        • 1970-01-01
        相关资源
        最近更新 更多