Eine bestimmte Zeile aus einer großen .txt auslesen



  • Nehmen wir mal an, deine Datei hat 100.000 Zeilen, nehmen wir an jede Zeile hat maximal 40 Zeichen. Multiplizieren wir das mal: 4.000.000 characters. Das sind nach Adam Riese etwa 3.8 Megabyte an Daten ... was spricht dagegen das in einen Vector von Strings zu packen - dann hast du schnellen Zeilenweisen Zugriff über das [] oder .at() des Vektors.

    Das Bottleneck ist bei heutigen PCs in der Regel nicht der Speicher oder die Geschwindigkeit, sondern der IO-Zugriff (also genau das, was du da mit fseak oder dauerndem wiedereinlesen) machst. Ich arbeite mit csv-Dateien die haben (runlength-endcoded) 11-20 MB (~600 Messungen pro Sekunde, 1024 ints per Messung, 30 Sekunden und länger) und ziehe die Problemlos in den Speicher zum weiterarbeiten (das parsen dauert etwas ;). Bei popeliegen 8k-10k Zeilen a 40 char's (die du hier als Beispiel bringst) sind das gerade mal 380kb oder so an Daten. Pillekram - sozusagen. Achja und bezüglich der string->char-arrays ... TOLL. Du hast dann 4000 Zeilen die vielleicht nur 10 Zeichen lang sind, reservierst aber immer 40 dafür. Warum dann überhaupt sparen - string nutzt soviel wie nötig und nicht viel mehr.



  • volkard schrieb:

    MGOS schrieb:

    Ich habe jetzt ein char-Array genommen, da im Dokument auch Text steht.

    Das wird Werner sehr freuen.

    😃

    MGOS schrieb:

    char szText[40];
        datei.getline(szText,40);
    

    hallo MGOS, .. wo lernt man so in C++ zu programmieren (früher war ich gegen Bücherverbrennung)?

    MGOS schrieb:

    So, jetzt möchte ich willkürlich um dokument 'herumspringen' - d. h. ich lese und gebe zuerst Zeile 1486, dann Zeile 8621 und dann 2683 oder 5906 aus.
    ich möchte das Dokument zwischendurch nicht immer öffnen und schließen. Gibt es ein Funktion wie gotoxy() oder "\r" ?

    Ich könnte Dir sowas schnitzen, aber irgendwie ist das der falsche Ansatz - siehe auch den Beitrag von padreigh.

    Gruß
    Werner



  • padreigh schrieb:

    Achja und bezüglich der string->char-arrays ... TOLL. Du hast dann 4000 Zeilen die vielleicht nur 10 Zeichen lang sind, reservierst aber immer 40 dafür. Warum dann überhaupt sparen - string nutzt soviel wie nötig und nicht viel mehr.

    Mööp!
    Nehmen wir eine unwahrscheinliche Implementierung für std::string, die nur einen Zeiger auf begin und einen Zeiger auf end hat. Und netterweise 32-Bit-Zeiger.
    Dann sind das 8 Bytes für das String-Objekt. Und x Bytes, die Nutzdaten. Bei einer allocation granularity von 32 Byte(recht normal) werden das am Ende mindestens 40 Byte pro String gefressen.
    Copy-On-Write-Strings, wie bei mir, könnten es bei 36 Bytes belassen.
    Mir scheint, Dein Verschwendungsargument zeiht nicht.



  • Das mit Char-Array vs. String versteh ich - vielleicht ist es in der Tat besser, hier einen String zu verwenden. Man sagt jedoch, strings könne man schlechter manipulieren als Arrays...

    @ Werner:
    Falscher Ansatz, das versteh' ich auch, aber wie soll ich es sonst machen?

    das ganze Dokument auf einmal in ein Array/string fressen und die Indizes für jede Zeile speichern? Es muss doch irgendeine Möglichkeit geben, Bestimmte Stellen aus dem Dokument zu lesen.



  • Ok, ich geb zu, ich mag keine char* 's ;).

    Um den Overhead von 10k Zeilen in einem vector<string> zu verringern könnte man ja nur einen String nehmen, immer fleissig die Zeilen appenden und nur die Indexe in einen std::vector<int> speichern *g* Klasse drum, feddich. Oder man nimmt ihn einfach in Kauf *eg*.

    String schlechter manipulierbar als char arrays? Höh? Guck dir mal die std::string API an, die bietet eine Menge als einzeiler was man mir chars erst basteln muss!


  • Mod

    MGOS schrieb:

    Man sagt jedoch, strings könne man schlechter manipulieren als Arrays...

    Wer das sagt, hat einen Schaden. Einen großen noch dazu. Das Gegenteil ist der Fall.



  • Wenn du Zeile 7861 willst, warum nicht einfach:

    std::ifstream file(filename);
      int n = 1;
      while (file && n < 7861)
        if (file.get() == '\n')
          ++n;
      std::string line;
      std::getline(file, line);
    

    😕

    EDIT: Ok, praktisch das gleiche wurde ja schon gepostet...

    Wenn wirklich alle "Zeilen" maximal 40 Zeichen lang sein können und du oft so einen Zugriff brauchst würd ich mir aber wirklich (wie volkard schon angedeutet hat) überlegen ob du statt einer Textdatei nicht lieber einfach eine Binärdatei nimmst in der jede Zeile 40 Byte belegt. Dort kannst du dann nämlich einfach direkt zur Stelle Zeilennummer * 40 hinspringen ohne alles zu durchsuchen...



  • Ok, Textdatein sind aber denke ich mal einfacher für den user zu handeln (mit einem normalen Texteditor).

    Hier ist eine Lösung, die ich mit strings gefunden habe, und funktioniert wunderbar.

    #include <iostream> 
    #include <fstream> 
    #include <string> 
    
    using namespace std; 
    
    int main() 
    { 
        ifstream datei("test.txt");    //Datei öffnen
        string sLine;                  //Aktuelle Zeile
        string sText;                  //Ganzer Text
        sText.clear();                 //Löschen, falls etwas drinsteht
        do{
            getline(datei,sLine,'\n'); //eine Zeile auslesen
            sText += sLine + '\n';     //und dem Text hinzufügen +  Return
        }while (sLine != "");          //Solange rauskopiern bis zu einer leeren Zeile
        cout << sText;                 //Text ausgeben
        getchar();
        getchar();
        return 0; 
    }
    


  • MGOS schrieb:

    Ok, Textdatein sind aber denke ich mal einfacher für den user zu handeln (mit einem normalen Texteditor).

    Also gehts um maximal 10000 Zeilen und die ganzen Anforderungen waren nicht nötig?



  • MGOS schrieb:

    @ Werner:
    Falscher Ansatz, das versteh' ich auch, aber wie soll ich es sonst machen?

    das ganze Dokument auf einmal in ein Array/string fressen und die Indizes für jede Zeile speichern? Es muss doch irgendeine Möglichkeit geben, Bestimmte Stellen aus dem Dokument zu lesen.

    Indem Du kurz schilderst, was Dein Programm sonst so noch tut.
    Wo, wie und zu welchem Zeitpunkt erfährt Dein Programm aus welcher Zeile es etwas herauslesen soll? Was ist das, was da gelesen wird - wird das wirklich nur als Zeile genauso ausgegeben oder wird es irgendwie interpretiert und noch auf den Inhalt reagiert? Muss die Ausgabe sofort geschehen, oder kann das am Ende des Programms (bzw. später) gemacht werden?

    Gruß
    Werner



  • @ Volkard
    Ja, maximal 10000 Zeilen, mehr wird es nicht sein. Aber es sollte auch auf einem langsamen Rechner laufen können, und man nicht erst 15 Minuten warten muss bis es komplett bearbeitet wurde

    Das Programmm bekommt eine Textdatei mit beliebiger Größe, welche eventuell mit einem komplett unabhängigen andern Programm erstellt wurde.
    Die ersten 4 Zeichen einer Zeile stellen eine Art Zeilennummer dar.
    Die nächsten 4 Zeichen sind Codes für Befehle, was als nächstes gemacht werden soll.
    Dann folgen zuerst ein Leerzeichen, dann bis zu 30 Zeichen Text oder Zahlen.
    Das Programm erfährt entweder durch die Codes und die folgende Zahl, wohin es springen soll, oder durch eine Benutzereingabe.

    Die Zeilen sollten deshalb sofort ausgegeben werden, wenn sie erreicht wurden.

    Bespiel:

    5B7 GOTO 3CA
    


  • Touring mit nem endlichen Band?



  • padreigh schrieb:

    Touring mit nem endlichen Band?

    Warum müssen wir raten? Weil du uns nicht sagst um was es geht.

    Ich rate auch mal und sage, dass man solche Datenmassen am besten mit einer Datenbank löst und nicht mit Textdateien. Das ist wenigstens vernünftig.



  • Warum kannst du in dem Fall nicht einfach die ganze Datei einlesen und dann basierend darauf dein Programm ausführen? Überleg doch mal was für einen wahnsinnigen Overhead du sonst für wohl einer der wesentlichsten Funktionen überhaupt, nämlich einem simplen Jump Befehl, hast. Vor allem sind Jumps an den Anfang der Datei dann wesentlich schneller als Jumps an Stellen weiter hinten. Wenn du mich fragst ist das ziemlicher Mist...



  • Also wenn die Datei so aussieht, solltest Du den Inhalt auf einmal im Speicher halten. Aber bitte nicht als strings, denn Du brauchst keine strings, sondern als eine Struktur, die Deinen Anforderungen angemessen ist. Beim Einlesen einer 'Zeile' solltest Du gleich so eine Struktur füllen.

    Anbei ein Code-Schnipsel was in die Richtung geht, die ich meine:

    #include <algorithm> // find_if
    #include <fstream>
    #include <iostream>
    #include <string> //string, getline
    #include <vector>
    
    struct Cmd
    {
        int m_label;
        std::string m_cmdType; // Bem.: ggf. durch enum ersetzen, braucht auch weniger Platz & ist schneller
        std::string m_param; // Bem.: Optinmierungsbedarf (groß), aber wie sind die genaue Anforderungen?
    };
    // --  liest ein Cmd-Objekt vom istream
    std::istream& operator>>( std::istream& in, Cmd& cmd )
    {   // Format: <Label> <Kommanodwort> <Parameter>      // Bem.: Parameter darf nicht leer sein
        return std::getline( in >> cmd.m_label >> cmd.m_cmdType >> std::ws, cmd.m_param );
    }
    
    struct Line // Funktor für die Suche nach den Zeilen/Labels
    {
        Line( int lable=0 ) : m_label( lable ) {}
        bool operator()( const Cmd& cmd ) const
        {
            return cmd.m_label == m_label;
        }
    private:
        int m_label;
    };
    
    int main()
    {
        using namespace std;
        vector< Cmd > cmds;
        ifstream datei("input.txt");
        datei >> hex; // die Labels sind als HEX hinterlegt
        for( Cmd cmd; datei >> cmd; )
            cmds.push_back( cmd );
    
        // --   usw.
        //      z.B. eine bestimmte 'Zeile' suchen
        vector< Cmd >::iterator i = find_if( cmds.begin(), cmds.end(), Line( 0x3CA ) );
        if( i != cmds.end() )
        {
            cout << "naechstes Kommando ist: " << i->m_cmdType << endl;
        }
        return 0;
    }
    

    Gruß
    Werner



  • Hingeklatscht und faul ... einfach alles einlesen. Datei besteht aus 12001 identischen Zeilen "12345678 90 12345678 90 12345678 90 12345678 9" .. das braucht 6ms zum einlesen im Debugcompilat ... also kaum ein "Hinderungsgrund" es nicht zu tun. Stundenlang warten da höchsten Trolle.

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <string>
    #include <QTime>
    int main()
    {
        using namespace std;
    
        QTime time;
        time.start();
        vector<string> alles;
        alles.reserve(13000);
        {
            ifstream datei("test.txt");
            string line;
    
            while( datei.good() )
            {          
                getline(datei,line,'\n');
                alles.push_back(line);
                // std::cout << alles.size() << std::endl;
            }
        } // wer braucht schon exceptions ;)
    
        std::cout << time.elapsed();
    }
    


  • dot schrieb:

    . Vor allem sind Jumps an den Anfang der Datei dann wesentlich schneller als Jumps an Stellen weiter hinten.

    Ja, das hab ich mir auch schon überlegt. Ganze Datei einlesen, dann funktioniert es super, die Zeit/Speicher habe ich jetzt auch ausprobiert und das ist auch Ok.

    Danke für eure Hilfe



  • Wenn das ähnlich zu Assembler-Programmcode wird, dann gibt es aber nur sehr wenige Sprungziele. Und wenn nicht mit goto gesprungen wird, gehts sequenziell weiter zur Folgezeile. Dann müßte man fseek nur machen, wenn ein Sprung stattfindet. Bei 10000 Zeilen Code vielleicht nur 500 Sprungziele. Dann kannste auch nur die cachen.



  • Ja, ich werde nicht so viele Sprungbefehle brauchen, aber wichtig ist, das ich sie verwenden kann. Sonst lese ich normalerweise mit der nächsten Zeile weiter.



  • padreigh schrieb:

    ifstream datei("test.txt");
            string line;
    
            while( datei.good() )
            {          
                getline(datei,line,'\n');
                alles.push_back(line);
                // std::cout << alles.size() << std::endl;
            }
    

    Diese Konstruktion liest eine Zeile zu viel, wenn das letzte Zeichen der Datei ein '\n' ist. Was in der überschüssigen Zeile steht, ist nicht definiert und von der Implementierung von getline abhängig.

    Vor diesen while(good)-lese-verarbeite oder schlimmer while(!eof)-lese-verarbeite -Konstruktionen kann man nur warnen. Für das Einlesen gilt das, was bei jedem Funktionsaufruf gilt, der einen Fehler produzieren kann.
    ZUERST aufrufen(hier lesen) und DANACH prüfen, ob es gut gegangen ist - und nicht umgekehrt. Also wenn unbedingt mit while, dann

    ifstream datei("input.txt");
            string line;
            while( getline(datei,line,'\n') )
            {          
                alles.push_back(line);
                // std::cout << alles.size() << std::endl;
            }
    

    Gruß
    Werner


Anmelden zum Antworten