2009-09-04 47 views
9

Creo que he encontrado una manera muy eficiente de leer archivos muy, muy grandes línea por línea. Por favor dígame si sabe de una manera mejor/más rápida o vea un margen de mejora. Estoy tratando de mejorar en la codificación, por lo que cualquier tipo de consejo que tengas sería agradable. Esperemos que esto sea algo que otras personas puedan encontrarle útil también.¿Qué es una forma superrápida de leer archivos grandes línea por línea en VBA?

Parece ser algo así como 8 veces más rápido que usar entrada de línea de mis pruebas.

'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 

¡Gracias por el asesoramiento!

Respuesta

2

Con ese código, carga el archivo en la memoria (como una cadena grande) y luego lee esa línea línea por línea.

Al usar Mid $() e InStr() realmente lee el "archivo" dos veces, pero como está en la memoria, no hay ningún problema.
No sé si el String de VB tiene un límite de longitud (probablemente no), pero si los archivos de texto tienen un tamaño de cientos de megabytes, es probable que disminuya el rendimiento debido al uso de la memoria virtual.

+0

Este es un muy buen punto. Estaba exagerando ingenuamente al usar dos muy. El tamaño de los archivos que estoy usando son de cinco a diez megabytes, y nunca más de cincuenta. – Justin

+1

** La longitud máxima de una cadena de longitud variable ** en VB y VBA es de aprox. ** 2 mil millones de caracteres ** (también conocido como 2GB). (Fuente: [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

+0

@ashleedawg, gracias por la información. Corrección leve: el límite es de 4 GB, ya que el tamaño de cada carácter es de 2 bytes (unicode). –

1

Creo que en un escenario de archivo grande, el uso de una transmisión sería mucho más eficiente, ya que el consumo de memoria sería muy pequeño.

Pero su algoritmo podría alternar entre usar una secuencia y cargar todo en la memoria en función del tamaño del archivo. No me sorprendería si uno es mejor que el otro bajo ciertos criterios.

+0

Este también es un punto excelente, y he descubierto que es especialmente cierto si solo necesita leer información desde el inicio del archivo; en este caso, usar una transmisión sería mucho, mucho mejor. Además, es bueno que saques el tema de la memoria porque no soy especialmente consciente de los efectos del uso de memoria cuando programo, pero supongo que eso es solo una consecuencia de que soy un novato. – Justin

11

Puede usar Scripting.FileSystemObject para hacer eso. Desde el Reference:

El método ReadLine permite un script para leer las líneas individuales en un archivo de texto. Para usar este método, abra el archivo de texto y luego configure un Do Do que continúe hasta que la propiedad AtEndOfStream sea True. (Esto simplemente significa que ha llegado al final del archivo.) Dentro del Do Do, llame al método ReadLine, almacene el contenido de la primera línea en una variable y luego realice alguna acción. Cuando la secuencia de comandos se desplaza, se abrirá automáticamente una línea y leerá la segunda línea del archivo en la variable. Esto continuará hasta que cada línea haya sido leída (o hasta que la secuencia de comandos salga específicamente del ciclo).

Y un ejemplo rápido:

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

Este es otro punto interesante. Mi (relativamente limitada) prueba ha demostrado que este fue en realidad el método más lento de los tres. Abrir los archivos como una secuencia con el FSO tomó mucho más tiempo que la apertura con un identificador de archivo entero, y tomó casi la misma cantidad de tiempo que leer todo el archivo en una cadena. Cuando se trataba de leer línea por línea, también era más lento ... si mal no recuerdo, de todos modos; Ha pasado un tiempo desde que hice mis pruebas y publiqué todo esto. – Justin

+1

¿Ha probado solo la lectura del archivo o la lectura del archivo y la concatenación? He escrito aplicaciones que usan filesystemobject para cargar archivos enormes (más de 400MB) y nunca demoraron demasiado (no más de unos pocos segundos para cargar todo el archivo). Recuerde, la concatenación de cadenas siempre es lenta, a menos que implemente la concatenación mediante matrices. – Rodrigo

1

'puede modificar arriba y leer el archivo completo de una sola vez y luego mostrar cada línea como se muestra a continuación

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

obras de entrada de línea bien para archivos pequeños. Sin embargo, cuando los tamaños de archivo alcanzan los 90k, Line Input salta por todos lados y lee los datos en el orden incorrecto del archivo de origen. lo probé con diferentes filesizes:

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

lección aprendida - utilizar Scripting.FileSystemObject

+1

Si el archivo tiene algún tipo de estructura, un conjunto de registros es muy útil. Puede usar el controlador de texto de Microsoft para crear uno. – Fionnuala

9

Mis dos centavos ...

No hace mucho tiempo que necesitaba leer archivos usando VBA y se dio cuenta esta cuestión.Probé los tres enfoques para leer datos de un archivo para comparar su velocidad y confiabilidad para una amplia gama de tamaños de archivo y longitudes de línea. Los enfoques son:

  1. Line Input VBA comunicado
  2. Usando el objeto de sistema de archivos (FSO)
  3. Usando Get declaración VBA para todo el archivo y luego analizar la cadena leer como se describe en los puestos aquí

Cada caso de prueba consta de tres pasos: configuración caso

  1. prueba que escribe un archivo de texto que contiene el número dado de líneas de la misma longitud dada llena por el patrón de caracteres conocido.
  2. Prueba de integridad. Lea cada línea de archivo y verifique su longitud y contenido.
  3. Prueba de velocidad de lectura del archivo. Lea cada línea del archivo repetida 10 veces.

Como puede ver, el Paso 3 comprueba la velocidad de lectura del archivo verdadero (como se pregunta en la pregunta) mientras que el Paso 2 verifica la integridad de lectura del archivo y simula condiciones reales cuando se necesita analizar cadenas.

El siguiente cuadro muestra los resultados de la prueba de velocidad de lectura del archivo. El tamaño del archivo es de 64M de bytes para todas las pruebas, y las pruebas difieren en la longitud de línea que varía desde 2 bytes (sin incluir CRLF) hasta 8M de bytes.

No idea why it is not displayed any longer :(

CONCLUSIÓN:

  1. Todos los tres métodos son fiables para archivos de gran tamaño con longitudes normales y anormales de línea (por favor, comparar a Graeme Howard’s answer)
  2. Todos los métodos producen la lectura de archivos casi equivalente velocidad para longitudes de línea normales
  3. "Modo superrápido" (Método n. ° 3) funciona bien para líneas extremadamente largas, mientras que las otras dos no.
  4. Todo esto es aplicable a las distintas oficinas, ordenadores diferentes, para VBA y Visual Basic 6
+1

Ejemplo del método 3: http://stackoverflow.com/a/19412682/4691433 – puzzlepiece87

+0

información excelente, gracias. – ashleedawg

0

Mi opinión sobre ella ... obviamente, tienes que hacer algo con los datos que se lee en. Si se trata de escribirlo en la hoja, será mortalmente lento con un For Loop normal. Se me ocurrió lo siguiente basado en una repetición de algunos de los artículos allí, más algo de ayuda del sitio web de Chip Pearson.

lectura en el archivo de texto (suponiendo que no conoce la longitud de la gama que va a crear, por lo que sólo se da la 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 

el contrario, si tiene que escribir a cabo una serie a un archivo de texto, esto lo hace rápidamente en una declaración de impresión (nota: el tipo de archivo 'Abrir' aquí está en modo texto, no binario ... a diferencia de la rutina de lectura anterior).

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

Tenga cuidado al usar Application.Transpose con un gran número de valores. Si transpone los valores a una columna, Excel asumirá que está asumiendo que los ha transpuesto de las filas.


Max Columna Límite Límite < Max Row, y que sólo mostrará la primera (Límite Max Columna) valores, y anithing después de que habrá "N/A"

Cuestiones relacionadas