VB.NET(2013) - 检查字符串对大文件

时间:2016-02-02 16:30:02

标签: vb.net filesize

我有一个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和内存,但我不知道知道这个级别的编程。我想要的只是让你看看并提供你所有或部分代码的最佳答案。告诉我你认为什么会让它变得更好,什么对一行或全部有帮助。我没有时间限制,因为代码有效,尽管速度很慢,我只想告诉我哪些代码可以更好,我可以做些什么来绕过庞大的文件大小。

1 个答案:

答案 0 :(得分:0)

您的代码很慢,因为它正在执行很多的文件IO。通过一次读一行,你就走上了正确的轨道,但这可以改进。

首先,我根据您提供的数据创建了一些测试文件。这些文件包含三百万行,大小约为130 MB(220万条记录小于100 MB,所以我增加了行数来达到你声明的文件大小)。

将整个文件读入单个字符串会占用大约600 MB的内存。用两个文件(我假设你正在做)并且你使用了超过1GB的内存,这可能导致崩溃(你不会说出崩溃发生时显示的错误,如果有的话)所以我只能假设它是OutOfMemoryException)。

在我查看代码之前,这里有一些提示:

使用Using

这对性能没有帮助,但它确实使您的代码更清晰,更易于阅读。

每当您处理文件(或任何实现IDisposable界面的内容)时,使用Using statement始终是个好主意。即使发生错误,这也会自动处理文件(关闭文件)。

不要使用FileOpen

FileOpen方法已过时(甚至声称其文档速度很慢)。您已经(几乎)使用了更好的替代方案:StreamWriterStreamReader的表兄弟)。

打开和关闭文件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秒才能运行。