2009-09-04 206 views
9

我相信我已经想出了一种非常有效的方式来逐行读取非常大的文件。请告诉我,如果你知道更好/更快的方式或看到改进的余地。我试图在编码方面做得更好,所以你的任何建议都会很好。希望这是其他人可能会觉得有用的东西。什么是在VBA中逐行读取大文件的超快方法?

它似乎比使用我的测试中的线路输入快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 

感谢您的建议!

回答

2

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

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

+0

这是一个非常好的点。我非常天真地使用两个非常夸张的东西。我使用的文件的大小大约是五到十兆,并且从不超过五十。 – Justin 2009-09-09 14:05:01

+1

在VB和VBA中,可变长度字符串**的**最大长度约为。 ** 20亿字符**(又名2GB)。 (来源:[VBA](https://msdn.microsoft.com/en-us/vba/language-reference-vba/articles/data-type-summary)&[VB](https://docs.microsoft。 com/en-us/dotnet/visual-basic/language-reference/data-types/data-type-summary)) – ashleedawg 2018-01-02 07:00:36

+0

@ashleedawg,感谢您的信息。稍微修正:由于每个字符的大小为2个字节(unicode),所以限制为4GB。 – 2018-01-02 10:12:30

1

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

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

+0

这也是一个很好的观点,我发现,如果您只需要从文件的开头读取信息,就更是如此;在这种情况下,使用流将会好很多。另外,你提出内存问题是很好的,因为我在编程时并没有特别意识到内存使用的影响,但我想这只是我成为新手的一个后果。 – Justin 2009-09-09 14:09:47

11

您可以使用Scripting.FileSystemObject来做那件事。 从Reference

ReadLine方法允许脚本读取的文本文件单独的行。要使用此方法,请打开文本文件,然后设置一个Do循环,直到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 
+2

这是另一个有趣的观点。我的(相对有限的)测试表明,这实际上是三者中最慢的方法。使用FSO作为流打开文件比使用整型文件句柄打开需要更多的时间,并且将整个文件读入字符串花费的时间大致相同。当它实际上逐行阅读时,它也变得更慢......如果我没有记错的话,无论如何;自从我做了测试并发布了所有这一切以来,这已经有一段时间了。 – Justin 2009-09-09 14:15:03

+1

您是否仅测试文件读取或文件读取和连接?我编写的应用程序使用filesystemobject来加载超大文件(超过400MB),并且不会花太长时间(加载整个文件的时间不会超过几秒)。 请记住,字符串连接总是很慢,除非您使用数组实现连接。 – Rodrigo 2009-09-09 19:13:51

1

“你可以在上面修改和读取完整的文件在一个去 ,然后显示每行如下图所示

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 
5

线路输入作品适用于小文件。但是,当文件大小达到90k左右时,线路输入会跳到所有位置,并以错误的顺序从源文件中读取数据。 我有不同的filesizes测试它:

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

教训教训 - 使用Scripting.FileSystemObject的

+1

如果文件有任何种类的结构,记录集非常方便。您可以使用Microsoft文本驱动程序来创建一个。 – Fionnuala 2012-10-03 12:53:04

9

我的两分钱......

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

  1. Line Input VBA语句
  2. 使用文件系统对象(FSO)
  3. 使用Get VBA语句整个文件,然后解析在帖子这里
描述读取字符串

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

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

正如你可以看到,第3步验证其真实文件读取速度(如问的问题),而第2步验证文件读取的完整性,并因此需要字符串解析时,模拟真实情况。

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

No idea why it is not displayed any longer :(

结论:

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

方法3的示例:http://stackoverflow.com/a/19412682/4691433 – puzzlepiece87 2015-08-04 17:39:08

+0

优秀的信息,谢谢。 – ashleedawg 2018-01-02 07:01:16

0

我就带它......很明显,你必须做你读入。该数据的东西,如果它涉及将它写到表单上,这会在正常的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 
0

使用具有大量值的Application.Transpose时要小心。如果你将值转置到一列,excel会假设你假设你将它们从行中移走。


最大列限制<最大行限制,并且它将只显示第一(最大列限制)值,以及后anithing,这将是“N/A”

相关问题