Schwierigkeiten beim Schreiben einzelner Zeilen in eine Datei


DS83

Grünschnabel
#1
Hallo Leute!

Habe ein paar Trööts zum Thema dyn Arrays gelesen, bin aber noch nicht ganz zufrieden, weil ich das noch nicht so richtig auf meine Problemstellung anwenden kann. Habe zwar mal Fachinformatiker gelernt aber das war zu einer Zeit, als wir das Licht noch mit dem Hammer ausgemacht haben. Also verzeiht bitte, dass ich offensichtlich etwas eingerostet bin.

Zu meinem Problemchen:
Mittlerweile arbeite ich im 3D-Bereich. Das Programm mit dem ich die 3D-Daten beackere speichert den Strukturbaum der Baugruppen und Bauteile neben ungefähr einer Fantastillionen anderer Informationen in einer XML-Datei ab. So weit, so gut. Ich habe mir schon ein Exportprogramm geschrieben, welches mir aus der XML-Datei NUR die Daten liefert, die für mich relevant sind, plus der Zeilennummer aus der XML-Datei als Zeiger und in eine *.csv-Datei schreibt. Die Daten kann ich dann in Excel bearbeiten. Jetzt möchte ich die Daten zurück in die XML schreiben und da gehts los:
Ich kann keinen Text mitten in die Datei schreiben. Nur anhängen oder die Datei komplett überschreiben, sehe ich das richtig?
Also das überschreiben einzelner Zeilen wäre mir deutlich lieber!
Wenn ich aber die Datei überschreiben soll, muss ich vorher die ganzen anderen Zeilen zwischenspeichern und dann von diesem Array an char-Arrays die geänderten Zeilen ersetzen. Die max Zeilenlänge ist bekannt aber die Zeilenanzahl variiert von Datei zu Datei extrem! Also das kürzeste, was ich bisher hatte waren nur ca. 5.000 Zeilen in der XML, das Längste waren bisher knapp 300.000!!! Und auch das ist noch beliebig steigerbar, sollte es sich um ein komplettes Fahrzeug handeln. Also ich möchte nicht für jeden Pups auf Verdacht ein Array von 1 mio. Zeilen anlegen, zumal ich nicht sicher sagen kann, dass nicht mal eine noch größere Datei daher kommt. Mit einer kleinen Schleife wären die Zeilen ja schnell gezählt aber wie mach ich dann weiter?

Kann mir jemand helfen?
Wenn nötig stellt auch gerne erst noch Rückfragen, ich hoffe ich kann sie beantworten!

Grüße,
Daniel
 

cwriter

Erfahrenes Mitglied
#2
Ich kann keinen Text mitten in die Datei schreiben. Nur anhängen oder die Datei komplett überschreiben, sehe ich das richtig?
Das ist korrekt. Das hat aber nichts mit Programmiersprachen zu tun, sondern mit der Blockstruktur von Dateien (die allermeisten Dateisysteme geben dir einen Block von Daten. Den Inhalt des Blocks kannst du frei verändern, nur verschieben musst du dann halt auch selbst).
Also ich möchte nicht für jeden Pups auf Verdacht ein Array von 1 mio. Zeilen anlegen, zumal ich nicht sicher sagen kann, dass nicht mal eine noch größere Datei daher kommt.
Wenn ich dich richtig verstanden habe, musst du das ja auch nicht - du hast ja deine Startzeile (und damit auch die Startposition in Bytes ab Beginn). Der Bereich [0;StartZeilenByte[ wird ja schon mal sicher nicht verändert.
Dann kommt der Block, in dem du Zeilen ändern willst, in [StartZeilenByte;EndZeilenByte[. Und dann kommt [EndZeilenByte;DateiEnde[ als letztes.

Die i.d.R. einfachste Art, sowas zu lösen, ist per Kopie und dann das Original zu löschen.
Also als Skizze:
  1. FileIn = original, FileOut = ausgabe
  2. Kopiere [0;StartZeilenByte[ von FileIn nach FileOut
  3. Kopiere veränderte Daten von Memory nach FileOut (appending)
  4. Kopiere [EndZeilenByte;DateiEnde[ von FileIn nach FileOut
  5. rm FileIn && mv FileOut FileIn
Das ganze geht natürlich auch inline (du kannst von EndZeilenByte bis Dateiende natürlich auch nach hinten schieben), aber dann musst du selbst wieder geeignete Grössen finden (i.d.R. ist das Kopieren in Blöcken von 4KB nahezu ideal).

Also ich möchte nicht für jeden Pups auf Verdacht ein Array von 1 mio. Zeilen anlegen, zumal ich nicht sicher sagen kann, dass nicht mal eine noch größere Datei daher kommt.
Das ist in der Tat eine schlechte Idee. Aber 2 Dinge fallen mir hier auf:
"Arrays" von Zeilen sind fast immer eine sehr schlechte Idee. Besser ist, die Datei als "Array von Bytes" zu lesen (i.e. char*) und dann die '\n' (newlines) zu finden. Dann kannst du 2 Arrays haben, einen mit den Daten und einen mit den Zeilenpositionen. Damit sparst du dir sehr viel Mühen und Ärger.

(falls du auf fgets() abzielst: Selbst diese Funktion kann problematisch sein, da du ja eine maximale Zeilenlänge angeben musst. fscanf() mit den POSIX m-Extensions ist da besser, aber sowohl fgets als auch fscanf sind sehr langsam. Wenn Performance wichtig ist, geht eigentlich nur mmap() oder fread()).

Ist diese Antwort etwa in deinem Sinne?

Gruss
cwriter
 
Gefällt mir: DS83

DS83

Grünschnabel
#3
@cwriter :
Vielen Dank für die schnelle Antwort! Mit dem unteren Teil (das mit POSIX m -Extensions usw.) kann ich leider nix anfangen...
Das mit dem Zeilenweisen kopieren klingt aber echt super. Denn ja, ich arbeite mit fgets. Die Idee ist mir wohl nicht selbst gekommen, weil sie genauso einfach ist, wie sie gut ist... Performance ist eher zweitranging. Notfalls starte ich das Abends und am nächsten morgen ist es dann fertig. Glaube aber nicht, dass das bei einem flammneuen i7 dazu kommen sollte :D
Wollte auf die Variante mit dem statischen Array nur verzichten, weil das ja für meinen Fall die mit Abstand "unsauberste" weil unflexible Programmiervariante ist! Also quasi die Holzhammer-Methode :ROFLMAO:

Gruß
Daniel
 

cwriter

Erfahrenes Mitglied
#4
Mit dem unteren Teil (das mit POSIX m -Extensions usw.) kann ich leider nix anfangen...
Hier wird das einigermassen ausführlich erklärt:
difference between %ms and %s scanf
In Kurzform: "%ms" statt "%s" erlaubt, einen string unbekannter Grösse einzulesen, scanf holt sich den benötigten Speicher selbst. Oder, für Zeilenweises lesen: fscanf(f, "%m[^\n]", &buf_ptr); (oder so).

Notfalls starte ich das Abends und am nächsten morgen ist es dann fertig.
Selbst bei mehreren GB reden wir hier von Minutenunterschieden, nicht Stunden. ;)
Glaube aber nicht, dass das bei einem flammneuen i7 dazu kommen sollte :D
Der Prozessor hat darauf wenig bis keine Auswirkung - Dateioperationen sind allermeistens System Calls, die dann auch noch synchronisiert werden müssen (vor allem bei Dateizugriffen). Also hilft eine schnelle CPU zwar, aber wenig im Vergleich zu SSD statt HDD und dergleichen.

Wollte auf die Variante mit dem statischen Array nur verzichten, weil das ja für meinen Fall die mit Abstand "unsauberste" weil unflexible Programmiervariante ist!
Mit fgets() brauchst du aber immer noch einen Array mit fixer Grösse :) Klar kannst du die Grösse feststellen, aber ist halt etwas mehr Arbeit. Bei C++ gibt es std::getline, welches dir die Arbeit abnimmt (automatische Zeilenlänge). Zusammen mit std::vector wird das ein ziemlich schnell geschriebenes Programm, wo dann aber eine gewisse Sorge bei zu grossen Dateigrössen entsteht. (Und es ist ein ziemliches Stück langsamer als fread() und selbst fgets()).

Gruss
cwriter