【问题标题】:What is a superfast way to read large files line-by-line in VBA?在 VBA 中逐行读取大文件的超快方法是什么?
【发布时间】:2018-08-17 04:25:38
【问题描述】:

我相信我已经想出了一种非常有效的方法来逐行读取非常非常大的文件。如果您知道更好/更快的方法或看到改进的余地,请告诉我。我正在努力提高编码能力,所以你有任何建议都会很好。希望这对其他人也有用。

它似乎比我的测试中使用 Line Input 快 8 倍。

'This function reads a file into a string.                        '
'I found this in the book Programming Excel with VBA and .NET.    '
Public Function QuickRead(FName As String) As String
    Dim I As Integer
    Dim res As String
    Dim l As Long

    I = FreeFile
    l = FileLen(FName)
    res = Space(l)
    Open FName For Binary Access Read As #I
    Get #I, , res
    Close I
    QuickRead = res
End Function

'This function works like the Line Input statement'
Public Sub QRLineInput( _
    ByRef strFileData As String, _
    ByRef lngFilePosition As Long, _
    ByRef strOutputString, _
    ByRef blnEOF As Boolean _
    )
    On Error GoTo LastLine
    strOutputString = Mid$(strFileData, lngFilePosition, _
        InStr(lngFilePosition, strFileData, vbNewLine) - lngFilePosition)
    lngFilePosition = InStr(lngFilePosition, strFileData, vbNewLine) + 2
    Exit Sub
LastLine:
    blnEOF = True
End Sub

Sub Test()
    Dim strFilePathName As String: strFilePathName = "C:\Fld\File.txt"
    Dim strFile As String
    Dim lngPos As Long
    Dim blnEOF As Boolean
    Dim strFileLine As String

    strFile = QuickRead(strFilePathName) & vbNewLine
    lngPos = 1

    Do Until blnEOF
        Call QRLineInput(strFile, lngPos, strFileLine, blnEOF)
    Loop
End Sub

感谢您的建议!

【问题讨论】:

    标签: vba file-io


    【解决方案1】:

    我的两分钱……

    不久前,我需要使用 VBA 读取大文件并注意到这个问题。我测试了从文件中读取数据的三种方法,以比较其在各种文件大小和行长下的速度和可靠性。方法是:

    1. Line InputVBA声明
    2. 使用文件系统对象 (FSO)
    3. 对整个文件使用Get VBA 语句,然后按照此处帖子中的说明解析读取的字符串

    每个测试用例包含三个步骤:

    1. 测试用例设置写入一个文本文件,其中包含由已知字符模式填充的相同给定长度的给定行数。
    2. 完整性测试。读取每个文件行并验证其长度和内容。
    3. 文件读取速度测试。重复读取文件的每一行 10 次。

    如您所见,步骤 #3 验证真实的文件读取速度(如问题中所要求的),而步骤 #2 验证文件读取完整性,因此在需要字符串解析时模拟真实情况。

    下图显示了文件读取速度测试的测试结果。所有测试的文件大小为 64M 字节,测试的行长从 2 字节(不包括 CRLF)到 8M 字节不等。

    结论:

    1. 对于正常和异常行长的大文件,这三种方法都是可靠的(请比较Graeme Howard’s answer
    2. 这三种方法在正常行长度下产生几乎相同的文件读取速度
    3. “超快速方式”(方法 #3)适用于极长的线路,而其他两种方式则不行。
    4. 所有这些都适用于不同的办公室,不同的 PC,适用于 VBA 和 VB6

    【讨论】:

      【解决方案2】:

      您可以使用 Scripting.FileSystemObject 来做这件事。 来自Reference

      ReadLine 方法允许脚本读取文本文件中的各个行。要使用此方法,请打开文本文件,然后设置一个循环,直到 AtEndOfStream 属性为 True。 (这仅仅意味着您已经到达文件的末尾。)在 Do 循环中,调用 ReadLine 方法,将第一行的内容存储在一个变量中,然后执行一些操作。当脚本循环播放时,它会自动下拉一行并将文件的第二行读入变量中。这将一直持续到每一行都被读取(或直到脚本明确退出循环)。

      还有一个简单的例子:

      Set objFSO = CreateObject("Scripting.FileSystemObject")
      Set objFile = objFSO.OpenTextFile("C:\FSO\ServerList.txt", 1)
      Do Until objFile.AtEndOfStream
       strLine = objFile.ReadLine
       MsgBox strLine
      Loop
      objFile.Close
      

      【讨论】:

      • 这是另一个有趣的点。我的(相对有限的)测试表明,这实际上是三者中最慢的方法。使用 FSO 将文件作为流打开比使用整数文件句柄打开文件花费的时间要长得多,并且与将整个文件读入字符串所花费的时间大致相同。当涉及到实际逐行阅读时,它也比较慢……如果我没记错的话,无论如何;自从我进行测试并发布所有这些以来已经有一段时间了。
      • 你是只测试文件读取还是文件读取和连接?我编写的应用程序使用 filesystemobject 加载大文件(超过 400MB)并且从不花费太长时间(加载整个文件不超过几秒钟)。请记住,字符串连接总是很慢,除非您使用数组实现连接。
      • 奇怪的是,这是最受好评的答案,因为它忽略了问题(有人有 faster 代码吗?),并盲目地提供最慢的代码作为“答案” (根据其他人的实际测试)。
      【解决方案3】:

      行输入适用于小文件。但是,当文件大小达到 90k 左右时,Line Input 会到处跳转,并以错误的顺序从源文件中读取数据。 我用不同的文件大小对其进行了测试:

      49k = ok
      60k = ok
      78k = ok
      85k = ok
      93k = error
      101k = error
      127k = error
      156k = error
      

      经验教训 - 使用 Scripting.FileSystemObject

      【讨论】:

      • 如果文件有任何结构,记录集非常方便。您可以使用 Microsoft 文本驱动程序来创建一个。
      【解决方案4】:

      使用该代码将文件加载到内存中(作为一个大字符串),然后逐行读取该字符串。

      通过使用 Mid$() 和 InStr() 您实际上读取了两次“文件”,但由于它在内存中,所以没有问题。
      我不知道 VB 的 String 是否有长度限制(可能没有),但如果文本文件的大小为数百兆字节,由于虚拟内存的使用,性能可能会下降。

      【讨论】:

      • 这是一个非常好的观点。我用两个非常天真地夸大了。我使用的文件大小约为 5 到 10 兆字节,从不超过 50 兆。
      • VB 和 VBA 中的可变长度字符串的最大长度约为。 20 亿个字符(又名 2GB)。 (来源:VBA & VB
      • @ashleedawg,感谢您提供的信息。小幅修正:限制为 4GB,因为每个字符的大小为 2 个字节(unicode)。
      • @NickDandoulakis - 虽然我当时为你的评论 +1,但我只是偶然回到这里,我很确定你弄错了。 VBA 确实以 UTF-16 存储,但据我了解,VBA 的字符串(和函数)最大为 2GB。讨论和测试here
      • ...此外,个别安装可能会有所不同。例如,32 位 Excel 的 hard limit 为 2GB,但有些 specific functions 可能能够超过该限制。顺便说一句,以 2GB 的总文件大小访问 mdbs 和 accdbs 以及 top out
      【解决方案5】:

      我认为,在大文件场景中,使用流会更有效率,因为内存消耗会非常小。

      但是您的算法可以根据文件大小在使用流和将整个内容加载到内存中交替使用。如果一个只在某些标准下比另一个更好,我不会感到惊讶。

      【讨论】:

      • 这也是一个很好的观点,我发现如果你只需要从文件开头读取信息,尤其如此;在这种情况下,使用流会好得多。另外,你提出内存问题很好,因为我在编程时并没有特别意识到内存使用的影响,但我想这只是我是新手的结果。
      【解决方案6】:

      '您可以修改上面的内容并一次读取完整文件 然后显示每一行如下图

      Option Explicit
      
      Public Function QuickRead(FName As String) As Variant
          Dim i As Integer
          Dim res As String
          Dim l As Long
          Dim v As Variant
      
          i = FreeFile
          l = FileLen(FName)
          res = Space(l)
          Open FName For Binary Access Read As #i
          Get #i, , res
          Close i
          'split the file with vbcrlf
          QuickRead = Split(res, vbCrLf)
      End Function
      
      Sub Test()
          ' you can replace file for "c:\writename.txt to any file name you desire
          Dim strFilePathName As String: strFilePathName = "C:\writename.txt"
          Dim strFileLine As String
          Dim v As Variant
          Dim i As Long
          v = QuickRead(strFilePathName)
          For i = 0 To UBound(v)
              MsgBox v(i)
          Next
      End Sub
      

      【讨论】:

        【解决方案7】:

        我对此的看法...显然,您必须对读入的数据进行处理。如果涉及将其写入工作表,则使用普通的 For 循环将非常缓慢。我根据那里的一些项目的重新整理以及来自 Chip Pearson 网站的一些帮助想出了以下内容。

        读取文本文件(假设你不知道它将创建的范围的长度,所以只给出了startingCell):

        Public Sub ReadInPlainText(startCell As Range, Optional textfilename As Variant)
        
           If IsMissing(textfilename) Then textfilename = Application.GetOpenFilename("All Files (*.*), *.*", , "Select Text File to Read")
           If textfilename = "" Then Exit Sub
        
           Dim filelength As Long
           Dim filenumber As Integer
           filenumber = FreeFile
           filelength = filelen(textfilename)
           Dim text As String
           Dim textlines As Variant
        
           Open textfilename For Binary Access Read As filenumber
        
           text = Space(filelength)
           Get #filenumber, , text
        
           'split the file with vbcrlf
           textlines = Split(text, vbCrLf) 
        
           'output to range
           Dim outputRange As Range
           Set outputRange = startCell
           Set outputRange = outputRange.Resize(UBound(textlines), 1)
           outputRange.Value = Application.Transpose(textlines)
        
           Close filenumber
         End Sub
        

        相反,如果您需要将一个范围写入文本文件,这可以在一个打印语句中快速完成(注意:这里的文件“打开”类型是文本模式,而不是二进制..不像上面的读取例程)。

        Public Sub WriteRangeAsPlainText(ExportRange As Range, Optional textfilename As Variant)
           If IsMissing(textfilename) Then textfilename = Application.GetSaveAsFilename(FileFilter:="Text Files (*.txt), *.txt")
           If textfilename = "" Then Exit Sub
        
           Dim filenumber As Integer
           filenumber = FreeFile
           Open textfilename For Output As filenumber
        
           Dim textlines() As Variant, outputvar As Variant
        
           textlines = Application.Transpose(ExportRange.Value)
           outputvar = Join(textlines, vbCrLf)
           Print #filenumber, outputvar
           Close filenumber
        End Sub
        

        【讨论】:

          【解决方案8】:

          在使用具有大量值的 Application.Transpose 时要小心。如果您将值转置到列中,excel 将假定您假设您从行中转置了它们。


          Max Column Limit

          【讨论】:

            【解决方案9】:

            我只是想分享一些我的结果...

            我有文本文件,显然来自 Linux 系统,所以我在每行末尾只有一个 vbLF/Chr(10) 而不是 vbCR/Chr(13)

            注 1:

            • 这意味着Line Input 方法将读取整个文件,而不是一次只读取一行。

            根据我在网络内外测试小型 (152KB) 和大型 (2778LB) 文件的研究,我发现以下内容:

            Open FileName For Input: Line Input最慢​​的 (参见上面的注 1

            Open FileName For Binary Access Read: Input 是读取整个文件的最快

            FSO.OpenTextFile: ReadLine ,但比 Binary Input 慢了一点

            注 2:

            • 如果我只需要检查文件头(前 1-2 行)来检查我是否有正确的文件/格式,那么 FSO.OpenTextFile最快,紧随其后的是Binary Input

            • Binary Input 的缺点是你必须知道有多少个字符 你想读。

            • 在普通文件上,Line Input 也是一个不错的选择 选项也是如此,但由于 注意 1,我无法测试。

             

            注 3:

            • 显然,网络上的文件在读取速度上的差异最大。他们还展示了第二次读取文件的最大好处(尽管这里肯定有内存缓冲区发挥作用)。

            【讨论】:

              猜你喜欢
              • 2021-12-08
              • 2011-12-23
              • 2013-01-17
              • 1970-01-01
              • 2012-10-20
              • 2020-10-06
              • 2011-08-13
              • 1970-01-01
              相关资源
              最近更新 更多