如何在F#中有效地写入文件?
我想生成大型xml文件以进行测试,但最终生成的代码非常慢,时间随着我写入文件的行数呈指数增长。下面的示例显示,写入100行需要毫秒,但写入1000行(在我的机器上)需要20秒以上。我真的不知道是什么原因导致速度变慢,因为我认为写1000行不会花那么长时间。而且,写200行的时间大约是写100行的4倍,这是不好的。要运行代码,您可能需要更改如何在F#中有效地写入文件?,f#,F#,我想生成大型xml文件以进行测试,但最终生成的代码非常慢,时间随着我写入文件的行数呈指数增长。下面的示例显示,写入100行需要毫秒,但写入1000行(在我的机器上)需要20秒以上。我真的不知道是什么原因导致速度变慢,因为我认为写1000行不会花那么长时间。而且,写200行的时间大约是写100行的4倍,这是不好的。要运行代码,您可能需要更改StreamWriter的路径 open System.IO open System.Diagnostics let xmlSeq = Seq.initInf
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"))
opensystem.IO
开放系统诊断
让xmlSeq=Seq.initInfinite(乐趣索引->sprintf“name%d%dbook%d”索引)
让createFile(seq:string seq)numberToTake文件名=
使用streamWriter=newstreamwriter(“C:\\tmp\\FSharpXmlTest\\FSharpXmlTest\\”+文件名,false)
streamWriter.WriteLine(“”)
let rec internalWriter(seq:string seq)(sw:StreamWriter)i(endTag:string)=
匹配
|0->(西南写入线(序号头部序号);
软件写入线(endTag))
|_u->(西南写入线(序号标题序号);
内部写入器(序列跳过1序列)sw(i-1)结束标记)
internalWriter seq streamWriter Number Take“”
让我们来看看=
让秒表=stopWatch.StartNew()
printfn“计时开始”
fn()
秒表
printfn“弹性时间:%A”秒表。已过
(funcTimer(fun()->createFile xmlSeq 100“file100.xml”))
(funcTimer(fun()->createFile xmlSeq 1000“file1000.xml”))
您在操作序列时观察到一种二次行为O(n^2)
。调用Seq.skip
时,将创建一个全新的序列,因此隐式遍历其余部分。更详细的解释见
在本例中,您不需要分解序列。将内部功能替换为:
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
应该更好地使用。在他的回答中指出了减速的原因。另一种惯用的方法可能是使用生成所需长度序列的无限序列,这使得代码非常简单:
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"))
让xmlSeq n=Seq.unfold(乐趣i->
如果i=0,则无
其他一些((sprintf“name%d%dbook%d”i),i-1))n
让createFile seqLen文件名=
使用streamWriter=newstreamwriter(“C:\\tmp\\FSharpXmlTest\\”+文件名,false)
streamWriter.WriteLine(“”)
seqLen |>xmlSeq |>Seq.iter streamWriter.WriteLine
streamWriter.WriteLine(“”)
(funcTimer(fun()->createFile 10000“file10000.xml”))
在我的笔记本电脑上生成10000个元素大约需要500毫秒。我想出了以下解决方案:
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
namespace-fsharp-basics
模块程序2=
开放系统
开放系统
开放系统诊断
让seqTest计数:seq=
让模板=”\
名称{0}\
{0}\
\
书籍{0}\
\
"
let行(i:int)=
String.Format(模板,i)
序号{
收益率“
对于[1..count]中的x,请执行以下操作
第x行产量
收益率“
}
[]
让主argv=
printfn“现在将写入文件”
让秒表=stopwatch.StartNew()
File.writeAllines(@“\test.xml”,seqTest 10000)|>忽略
秒表
printf“结束,用了%f秒”秒表。已用时间。总秒数
System.Console.ReadKey()|>忽略
0
在我的笔记本电脑上创建一个包含10000名作者的格式良好的test.xml文件不到90毫秒。感谢您澄清我每次都在创建一个新序列。但是,我应该替换内部函数,我可以删除内部函数并将for循环添加到外部函数。谢谢您填写其他有用的信息。其他答案更针对我的问题,但你提供了一些非常有用的额外信息。