2009-09-04 5 views
9

Wydaje mi się, że wymyśliłem bardzo skuteczny sposób, aby czytać bardzo, bardzo duże pliki wiersz po linii. Proszę powiedz mi, czy znasz lepszy/szybszy sposób lub widzisz możliwość poprawy. Próbuję poprawić kodowanie, więc każda rada, którą masz, byłaby miła. Mam nadzieję, że jest to coś, co inni mogą uznać za przydatne.Jaki jest superszybki sposób czytania dużych plików liniowo po linii w VBA?

Wydaje się to być coś jak 8 razy szybciej niż przy użyciu wejścia liniowego z moich testów.

'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 

Dzięki za poradę!

Odpowiedz

2

Z tym kodem załadować plik w pamięci (jak wielkim ciągiem), a następnie można przeczytać, że linia po linii ciąg.

Korzystając Mid $() i InStr() faktycznie przeczytać „plik” dwa razy, ale ponieważ jest to w pamięci, nie ma problemu.
ja nie wiem, czy VB String ma limitu długości (prawdopodobnie nie), ale jeśli pliki tekstowe są setki megabajtów wielkości to może nastąpić spadek wydajności ze względu na użycie pamięci wirtualnej.

+0

ten jest bardzo dobry punkt. Byłem dość naiwnie przesadzony, używając dwóch własnych. Rozmiar plików, których używam, wynosi od pięciu do dziesięciu megabajtów i nie więcej niż pięćdziesiąt. – Justin

+1

** Maksymalna długość łańcucha o zmiennej długości ** w VB i VBA wynosi ok. ** 2 miliardy znaków ** (inaczej 2 GB). (Źródło: [VBA] (https://msdn.microsoft.com/en-us/vba/language-reference-vba/articles/data-type-summary) i [VB] (https://docs.microsoft. com/en-us/dotnet/visual-basic/language-reference/data-types/data-type-summary)) – ashleedawg

+0

@ashleedawg, dzięki za informację. Nieznaczna korekta: limit wynosi 4 GB, ponieważ rozmiar każdego znaku wynosi 2 bajty (Unicode). –

1

Myślę, że w scenariuszu z dużym plikiem użycie strumienia byłoby znacznie bardziej wydajne, ponieważ zużycie pamięci byłoby bardzo małe.

Ale twój algorytm mógł przełączać pomiędzy pomocą strumienia i ładuje całą rzecz w pamięci na podstawie rozmiaru pliku. Nie zdziwiłbym się, gdyby jeden był lepszy od drugiego pod pewnymi kryteriami.

+0

Jest to również doskonały punkt, i stwierdziłem, że jest to szczególnie prawdziwe, jeśli wystarczy przeczytać informacje od początku pliku; w tym przypadku użycie strumienia byłoby znacznie, dużo lepsze. Poza tym dobrze, że poruszasz kwestię pamięci, ponieważ nie jestem szczególnie świadomy efektów użycia pamięci podczas programowania, ale myślę, że to tylko konsekwencja mojej nowości. – Justin

11

Można użyć Scripting.FileSystemObject zrobić tę rzecz. Od Reference:

metody ReadLine pozwala skrypt czytać poszczególne wiersze w pliku tekstowym. Aby użyć tej metody, otwórz plik tekstowy, a następnie skonfiguruj pętlę Do Loop, która będzie kontynuowana do momentu, w którym właściwość AtEndOfStream ma wartość True. (Oznacza to po prostu, że dotarłeś do końca pliku.) W pętli Do Lo wywołaj metodę ReadLine, przechowuj zawartość pierwszej linii w zmiennej, a następnie wykonaj jakąś akcję. Kiedy pętla skryptu się połączy, automatycznie opuści linię i odczyta drugą linię pliku do zmiennej. Będzie to kontynuowane aż do przeczytania każdej linii (lub do momentu, w którym skrypt wyjdzie z pętli).

i szybki przykład:

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

To kolejny interesujący punkt. Moje (względnie ograniczone) testy wykazały, że jest to najwolniejsza metoda z tych trzech. Otwarcie plików w strumieniu za pomocą FSO zajęło znacznie więcej czasu niż otwarcie za pomocą uchwytu pliku całkowitoliczbowego i zajęło mniej więcej tyle samo czasu, ile czytanie całego pliku w ciągu znaków. Kiedy chodziło o czytanie linii po linii, było też wolniej ... jeśli w ogóle pamiętam. Minęło trochę czasu odkąd zrobiłem moje testy i opublikowałem to wszystko. – Justin

+1

Czy przetestowano tylko odczyt pliku lub odczyt pliku i konkatenację? Napisałem aplikacje, które używają fileystemobject do ładowania ogromnych plików (ponad 400 MB) i nigdy nie trwały zbyt długo (nie więcej niż kilka sekund, aby załadować cały plik). Pamiętaj, że konkatenacja ciągów jest zawsze powolna, chyba że implementujesz konkatenację za pomocą tablic. – Rodrigo

1

„można zmodyfikować powyżej czytaj cały plik w jednym iść a następnie wyświetlić każdą linię, jak pokazano poniżej

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

prac wejście liniowe dobrze dla małych plików. Jednak gdy rozmiary plików sięgają około 90k, wejście liniowe przeskakuje w całym miejscu i odczytuje dane w niewłaściwej kolejności z pliku źródłowego. Testowałem go z różnymi filesizes:

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

Lekcja - użyj Scripting.FileSystemObject

+1

Jeśli plik ma jakąkolwiek strukturę, zestaw rekordów jest bardzo przydatny. Możesz użyć sterownika tekstowego Microsoft, aby go utworzyć. – Fionnuala

9

Moje dwa centy ...

Nie tak dawno temu potrzebowałem czytanie dużych plików przy użyciu VBA i zauważyłem to pytanie.Przetestowałem trzy podejścia do odczytu danych z pliku, aby porównać jego szybkość i niezawodność dla szerokiego zakresu rozmiarów plików i długości linii. Podejścia są:

  1. Line Input VBA oświadczenie
  2. Korzystanie z obiektu systemu plików (FSO)
  3. Korzystanie Get oświadczenie VBA dla całego pliku, a następnie analizowania ciąg przeczytać, jak to opisano w postach tutaj

Każdy przypadek testowy składa się z trzech etapów:

  1. konfiguracja testowa przypadku, zapisuje plik tekstowy zawierający podaną liczbę wierszy o tej samej zadanej długości wypełnionych znanym wzorcem znaków.
  2. Test integralności. Przeczytaj każdą linię pliku i sprawdź jej długość i zawartość.
  3. Test prędkości odczytu pliku. Czytaj każdą linię pliku powtarzaną 10 razy.

Jak można zauważyć, Krok # 3 weryfikuje prędkość odczytu prawdziwego pliku (zgodnie z pytaniem w pytaniu), podczas gdy Krok 2 weryfikuje spójność odczytu pliku, a zatem symuluje rzeczywiste warunki, gdy potrzebne jest parsowanie ciągów.

Poniższa tabela przedstawia wyniki testu dla testu prędkości odczytu pliku. Rozmiar pliku wynosi 64M bajtów dla wszystkich testów, a testy różnią się długością linii, która waha się od 2 bajtów (nie wliczając CRLF) do 8 milionów bajtów.

No idea why it is not displayed any longer :(

WNIOSEK:

  1. Wszystkie trzy metody są wiarygodne dla dużych plików z normalnych i nienormalnych długości linii (proszę porównać do Graeme Howard’s answer)
  2. Wszystkie trzy metody wytwarzają prawie równoważne odczytu pliku prędkość dla normalnej długości linii
  3. "Superszybka droga" (Metoda nr 3) działa dobrze w przypadku bardzo długich linii, podczas gdy pozostałe dwie nie.
  4. Wszystko to ma zastosowanie do różnych urzędów, różnych komputerach, na VBA i VB6
+1

Przykład metody 3: http://stackoverflow.com/a/19412682/4691433 – puzzlepiece87

+0

Doskonała informacja, dziękuję. – ashleedawg

0

My się na to ... oczywiście, że masz coś zrobić z danymi można przeczytać w., Jeśli to dotyczy zapisanie go do arkusza, który będzie śmiertelnie wolny z normalną pętlą For Loop. Wymyśliłem następujące, oparte na rehash niektórych elementów tam, plus trochę pomocy ze strony Chip Pearson.

Czytanie w pliku tekstowym (zakładając, że nie wiem, długość zakresie stworzy, więc tylko startingCell jest podany):

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 

Odwrotnie, jeśli trzeba napisać szereg do pliku tekstowego, robi to szybko w jednym poleceniu print (uwaga: plik "Open" jest tutaj w trybie tekstowym, a nie binarnie .. w przeciwieństwie do powyższej procedury odczytu).

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

Należy zachować ostrożność podczas korzystania z aplikacji. Przesyłaj dużą liczbę wartości. Jeśli transponujesz wartości do kolumny, program Excel zakłada, że ​​przenosisz je z wierszy.


maksymalna Kolumna granica granica < maksymalna rzędu i tylko wyświetlenie pierwszej (Max Limit kolumna) wartości, anithing później będzie "N/A"

Powiązane problemy