【问题标题】:How do I write effectively to a file in F#?如何在 F# 中有效地写入文件?
【发布时间】:2014-01-26 06:06:42
【问题描述】:

我想生成用于测试目的的大型 xml 文件,但我最终得到的代码真的很慢,时间随着我写入文件的行数呈指数增长。下面的示例显示写入 100 行需要几毫秒,但写入 1000 行需要 20 多秒(在我的机器上)。我真的不知道是什么让这变慢了,因为我认为写 1000 行不应该花那么长时间。此外,写入 200 行所需的时间大约是写入 100 行的 4 倍,这并不好。要运行代码,您可能需要更改 StreamWriter 的路径。

open System.IO
open System.Diagnostics

let xmlSeq = Seq.initInfinite (fun index -> sprintf "<author><name>name%d</name><age>%d</age><books><book>book%d</book></books></author>" index index index)

let createFile (seq: string seq) numberToTake fileName =
    use streamWriter = new StreamWriter("C:\\tmp\\FSharpXmlTest\\FSharpXmlTest\\" + fileName, false)
    streamWriter.WriteLine("<startTag>")
    let rec internalWriter (seq: string seq) (sw:StreamWriter) i (endTag:string) =
        match i with
        | 0 -> (sw.WriteLine(Seq.head seq);
            sw.WriteLine(endTag))
        | _ -> (sw.WriteLine(Seq.head seq);
            internalWriter (Seq.skip 1 seq) sw (i-1) endTag)
    internalWriter seq streamWriter numberToTake "</startTag>"

let funcTimer fn =
    let stopWatch = Stopwatch.StartNew()
    printfn "Timing started"
    fn()
    stopWatch.Stop()
    printfn "Time elased: %A" stopWatch.Elapsed


(funcTimer (fun () -> createFile xmlSeq 100 "file100.xml"))
(funcTimer (fun () -> createFile xmlSeq 1000 "file1000.xml"))

【问题讨论】:

    标签: f#


    【解决方案1】:

    您在操作序列时观察到 O(n^2) 的二次行为。当您调用Seq.skip 时,将创建一个全新的序列,因此您会隐式遍历剩余部分。更详细的解释可以在https://stackoverflow.com/a/1306267找到。

    在此示例中,您不需要分解序列。通过以下方式替换您的内部功能:

    let internalWriter (seq: string seq) (sw:StreamWriter) i (endTag:string) =
        for node in Seq.take i seq do
            sw.WriteLine(node)
        sw.WriteLine(endTag)
    

    我可以在几分之一秒内写入 10000 行。

    您可以通过删除此内部函数并将其主体复制到父函数来进一步重构。

    正如上面提到的链接,如果你需要分解序列,LazyList 应该更好用。

    【讨论】:

    • 感谢您澄清我每次都在创建一个新序列。但是,我应该替换内部函数,我可以删除内部函数并将 for 循环添加到外部函数。
    【解决方案2】:

    pad 在他的回答中指出了减速的原因。另一种惯用方法可能是使用Seq.unfold 代替无限序列生成所需长度的序列,这使得代码非常简单:

    let xmlSeq n = Seq.unfold (fun i ->
        if i = 0 then None
        else Some((sprintf "<author><name>name%d</name><age>%d</age><books><book>book%d</book></books></author>" i i i), i - 1)) n
    
    let createFile seqLen fileName =
        use streamWriter = new StreamWriter("C:\\tmp\\FSharpXmlTest\\" + fileName, false)
        streamWriter.WriteLine("<startTag>")
        seqLen |> xmlSeq |> Seq.iter streamWriter.WriteLine
        streamWriter.WriteLine("</startTag>")
    
    (funcTimer (fun () -> createFile  10000 "file10000.xml"))
    

    在我的笔记本电脑上生成 10000 个元素大约需要 500 毫秒。

    【讨论】:

    • 感谢您填写其他有用的信息。其他答案更针对我的问题,但您提供了一些非常有用的额外信息。
    【解决方案3】:

    我想出了以下解决方案:

    namespace FSharpBasics
    
    module Program2 =
    
        open System
        open System.IO
        open System.Diagnostics
    
        let seqTest count : seq<string> =
            let template = "<author>\
                        <name>Name {0}</name>\
                        <age>{0}</age>\
                        <books>\
                        <book>Book {0}</book>\
                        </books>\
                        </author>"
    
            let row (i: int) =
                String.Format (template, i)
    
            seq {
                yield "<authors>"
                for x in [ 1..count ] do
                    yield row x
                yield "</authors>"
            }
    
        [<EntryPoint>]
        let main argv =
            printfn "File will be written now"
    
            let stopwatch = Stopwatch.StartNew()
            File.WriteAllLines (@".\test.xml", seqTest 10000) |> ignore
            stopwatch.Stop()
    
            printf "Ended, took %f seconds" stopwatch.Elapsed.TotalSeconds
    
            System.Console.ReadKey() |> ignore
    
            0
    

    在我的笔记本电脑上创建一个包含 10,000 个作者的格式良好的 test.xml 文件只需不到 90 毫秒。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-02
      • 2013-11-19
      • 1970-01-01
      • 1970-01-01
      • 2018-07-08
      • 2016-11-25
      • 2015-04-18
      • 2020-11-25
      相关资源
      最近更新 更多