我有一个125Mb的文本文件,它包含220万条记录。我有另一个文本文件与原始文件不匹配,但我需要找出它的不同之处。通常情况下,对于较小的文件,我会读取每一行并以某种方式处理它,或者将整个文件读成字符串并同样地执行,但是这两个文件太大了,所以我想创建一些东西来实现我的目标。这就是我现在所拥有的...原谅它的混乱。
Private Sub refUpdateBtn_Click(sender As Object, e As EventArgs) Handles refUpdateBtn.Click
Dim refOrig As String = refOriginalText.Text 'Original Reference File
Dim refLatest As String = refLatestText.Text 'Latest Reference
Dim srOriginal As StreamReader = New StreamReader(refOrig) 'start stream of original file
Dim srLatest As StreamReader = New StreamReader(refLatest) 'start stream of latest file
Dim recOrig, recLatest, baseDIR, parentDIR, recOutFile As String
baseDIR = vb.Left(refOrig, InStrRev(refOrig, ".ref") - 1) 'find parent folder
parentDIR = Path.GetDirectoryName(baseDIR) & "\"
recOutFile = parentDIR & "Updated.ref"
Me.Text = "Processing Reference File..." 'update the application
Update()
If Not File.Exists(recOutFile) Then
FileOpen(55, recOutFile, OpenMode.Append)
FileClose(55)
End If
Dim x As Integer = 0
Do While srLatest.Peek() > -1
Application.DoEvents()
recLatest = srLatest.ReadLine
recOrig = srOriginal.ReadLine ' check the original reference file
Do
If Not recLatest.Equals(recOrig) Then
recOrig = srOriginal.ReadLine
Else
FileOpen(55, recOutFile, OpenMode.Append)
Print(55, recLatest & Environment.NewLine)
FileClose(55)
x += 1
count.Text = "Record No: " & x
count.Refresh()
srOriginal.BaseStream.Seek(0, SeekOrigin.Begin)
GoTo 1
End If
Loop
1:
Loop
srLatest.Close()
srOriginal.Close()
FileClose(55)
End Sub
它的编程和可怕的循环都很糟糕,但那是因为我不是一个专业的程序员,只是一个试图让他的生活更轻松的人。
目前,它使用表单插入原始文件和最新文件,并将匹配的每一行输出到新文件中。这不太完美,但我不知道如何处理大文件大小,因为 streamreader.readtoend 会导致程序崩溃。我也不需要输出是最新输入的副本,但我不知道如何只输出它找不到的记录。这是每个文件的记录样本:
doc:ARCHIVE.346CCBD3B06711E0B40E00163505A2EF
doc:ARCHIVE.346CE683B29811E0A06200163505A2EF
doc:ARCHIVE.346CEB15A91711E09E8900163505A2EF
doc:ARCHIVE.346CEC6AAA6411E0BEBB00163505A2EF
我目前正在运作的程序...时尚,但我知道有更好的方法可以做到这一点,我确信有更好的方法来使用CPU和内存,但我不知道知道这个级别的编程。我想要的只是让你看看并提供你所有或部分代码的最佳答案。告诉我你认为什么会让它变得更好,什么对一行或全部有帮助。我没有时间限制,因为代码有效,尽管速度很慢,我只想告诉我哪些代码可以更好,我可以做些什么来绕过庞大的文件大小。
答案 0 :(得分:0)
您的代码很慢,因为它正在执行很多的文件IO。通过一次读一行,你就走上了正确的轨道,但这可以改进。
首先,我根据您提供的数据创建了一些测试文件。这些文件包含三百万行,大小约为130 MB(220万条记录小于100 MB,所以我增加了行数来达到你声明的文件大小)。
将整个文件读入单个字符串会占用大约600 MB的内存。用两个文件(我假设你正在做)并且你使用了超过1GB的内存,这可能导致崩溃(你不会说出崩溃发生时显示的错误,如果有的话)所以我只能假设它是OutOfMemoryException
)。
在我查看代码之前,这里有一些提示:
Using
块这对性能没有帮助,但它确实使您的代码更清晰,更易于阅读。
每当您处理文件(或任何实现IDisposable
界面的内容)时,使用Using statement始终是个好主意。即使发生错误,这也会自动处理文件(关闭文件)。
FileOpen
方法已过时(甚至声称其文档速度很慢)。您已经(几乎)使用了更好的替代方案:StreamWriter
(StreamReader
的表兄弟)。
打开和关闭文件200万次(就像你在循环中做的那样)不会很快。这可以通过在循环外打开文件来改进。
DoEvents()
是邪恶的! DoEvents
是VB6时代的传统方法,它是您真正想要避免的一种方法,尤其是当您在循环中调用它200万次时! / p>
另一种方法是在单独的线程上执行所有文件处理,以便您的UI仍然响应。
在这里使用一个单独的线程可能有点过分了,你需要注意一些复杂的错误,所以我没有在下面的代码中使用单独的线程。
因此,让我们看一下代码的每个部分,看看我们可以改进哪些内容。
你几乎就在这里,但是你正在做一些你不需要做的事情。 GetDirectoryName
适用于文件名,因此无需先从原始文件名中删除扩展名。您还可以使用Path.Combine
方法组合目录和文件名。
recOutFile = Path.Combine(Path.GetDirectoryName(refOrig), "Updated.ref")
因为您在"最新"中的每一行循环文件并在"原始"中查找匹配项文件,您可以继续从"最新"文件。 但不是一次从"原始"文件,然后当你找到匹配时回到起点,你最好将所有这些行读入内存。
现在,不是将整个文件读入内存(如前所述占用600 MB),而是可以将文件的每一行读入一个数组。这会占用更少的内存,并且由于File
类而很容易做到。
originalLines = File.ReadAllLines(refOrig)
这将读取文件中的所有行并返回String
数组。在这个数组中搜索匹配将会很慢,因此我们可以读入HashSet(Of String)
而不是读入数组。这将占用更多的内存,但是通过它会更快。
originalLines = New HashSet(Of String)(File.ReadAllLines(refOrig))
因为我们现在拥有来自"原始"的所有行。数组中的行或HashSet
,搜索一行很容易。
originalLines.Contains(recLatest)
所以,让我们把所有这些放在一起:
Private Sub refUpdateBtn_Click(sender As Object, e As EventArgs)
Dim refOrig As String
Dim refLatest As String
Dim recOutFile As String
Dim originalLines As HashSet(Of String)
refOrig = refOriginalText.Text 'Original Reference File
refLatest = refLatestText.Text 'Latest Reference
recOutFile = Path.Combine(Path.GetDirectoryName(refOrig), "Updated.ref")
Me.Text = "Processing Reference File..." 'update the application
Update()
originalLines = New HashSet(Of String)(File.ReadAllLines(refOrig))
Using latest As New StreamReader(refLatest),
updated As New StreamWriter(recOutFile, True)
Do
Dim line As String
line = latest.ReadLine()
' ReadLine returns Nothing when it reaches the end of the file.
If line Is Nothing Then
Exit Do
End If
If originalLines.Contains(line) Then
updated.WriteLine(line)
End If
Loop
End Using
End Sub
这使用大约400 MB的内存,大约需要4秒才能运行。