Zitat von
Cosamia:
Jetzt soll z.B. der Wert, der an der neunten Stelle steht aggeändert werden.
Im Bedarfsfall soll erkannt werden, ob es sich um eine Dezimalzahl handelt oder nicht.
Was ist denn hier die neunte Stelle? Da steht doch im Moment ein ?, oder sehe ich das falsch?
An sich hast Du zwei Möglichkeiten:
1) Die Datei (wie hier) als Textfile zu betrachten. Dabei dient dann ein CR (Carriage-Return) und ggf. auch ein LF (Line-Feed) als Zeilenumbruch. Zudem wird alles was sich in der Datei befindet immer als Character interpretiert. Die ? sind dann mit relativ hoher Wahrscheinlichkeit keine echten ? sondern einfach Zeichen, deren Ordinaler Wert keine echte Entsprechung für ein sichtbares Zeichen hat (z.B. Steuerzeichen wie EOT, ACK, NAK, ...).
In diesem Fall solltest Du dann sagen, welche Zeile Du auf welche Art und Weise ändern möchtest. Für die Arbeit mit Textdateien (die nicht all zu groß sind) eignet sich die TStringList ganz gut. Nicht all zu groß heißt dann, dass die nur ein paar hunder KByte hat. Ich glaube die wird sonst recht langsam, aber schau einfach mal.
Mit der Funktion LoadFromFile kannst Du eine Datei laden, die dann in der TStringList landet. Dabei kannst Du über ihre Standard-Array-Eigenschaft gezielt auf Zeilen zugreifen:
Delphi-Quellcode:
var list: TStringList;
line: String;
begin
list := TStringList.Create;
list.LoadFromFile(...);
// speichert Zeile 11 in line
// Achtung, normalerweise sollte man natürlich prüfen,
// dass es überhaupt so viele Zeilen gibt!
// list.Count gibt Dir die Gesamtzahl zurück!
line := list[10];
Hier kannst Du mittels list[i] einfach auf die i-te Zeile zugreifen (lesend und schreibend). Änderungen landen aber erst in der Datei, wenn Du diese wieder abspeicherst (SaveToFile).
2) Alternativ zu Textdateien kannst Du eine Datei auch immer "Roh" betrachten. Das ist die Form, die sie tatsächlich auf der Festplatte hat. Eine Datei besteht eigentlich nur aus einer bestimmten Anzahl von Bytes, die Du beliebig interpretieren kannst. Ein Texteditor sieht eine Datei, die Du öffnest genau so. Er wandelt dann einfach alle Bytes in Characters um, wobei Character 10 und 13 dann eben gerade CR und LF entsprechen und als Zeilenumbruch interpretiert werden. Character 9 wäre z.B. der Tabulator, hier kann dann der Texteditor entscheiden was geschieht, wenn dieses Zeichen gefunden wird.
Eine andere Möglichkeit ist, die Bytes Sedezimal anzeigen zu lassen, bekannter ist das unter dem Namen Hex-Editor (Se ist
imho das korrekte Präfix, nicht hex). Jedenfalls wird hier einfach die Datei Byteweise angezeigt, so wie sie eben auch auf der Platte liegt. Um die Lesbarkeit für einen Menschen zu erhöhen, werden aber die Bytes noch schön formatiert (in einer Art Tabelle angeordnet und ihre Positionen dazu geschrieben).
Möchtest Du eine Datei in dieser Form lesen/schreiben, so kannst Du das mit Hilfe von Streams. Streams betrachten einfach Datenströme. Sie haben einen Zeiger, der auf die aktuelle Position im Stream zeigt und können Lesen oder Schreiben. Eine Datei stellt eine mögliche Quelle für einen solchen Strom da. Eine Datei ist nur ein einfacher Strom von Bytes.
In anderen Programmiersprachen ist es durchaus üblich, dass man einen Zeilenumbruch durch das einfügen von \n erreicht. Die Zeilen:
Code:
Ein String mit\n\fZeilenumbruch
und
Code:
Ein String mit
Zeilenumbruch
sind also eigentlich das selbe. Die obere Form entspricht aber dem, wie Du es in einer Datei (oder einem Stream) vorfinden würdest. Statt \n würde man in Delphi eher #10#13 schreiben, aber das Prinzip ist das Gleiche.
Bei der Arbeit mit einem Stream greifst Du also direkt auf ein Zeichen an einer bestimmten Stelle zu, Zeilen gibt es nicht (die sind reine Interpreationssache). Das erste Zeichen ist E, das 15te ist \n, usw.
Einen solchen Stream werden auch immer Daten als Bytes (uninterpretiert) hinzugefügt oder entnommen, es ist Sache des Benutzers diese geeignet zu interpretieren.
Delphi-Quellcode:
var s: TFileStream;
buffer: String;
begin
// Stream erzeugen, der auf die Datei FileName zugreift und diese
// zum lesen öffnet und vor Veränderungen schützt (andere dürfen
// natürlich gleichzeitig lesen!)
s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
// immer wichtig bei der Arbeit mit Dateien oder anderen Ressourcen
// ist ein Ressourcen-Schutzblock. Dieser sollte sicherstellen, dass
// eine Ressource auch im Fehlerfall nicht blockiert wird
try
// die Größe des Puffers setzen
setLength(buffer, 10);
// den Positionszeiger setzen
s.Position := 10;
// 10 Zeichen von der aktuellen Position aus der Datei in den Puffer lesen
s.read(buffer[1], 10);
finally
// hier die Freigabe des Streams auch im Fehlerfall
// und damit die garantierte Aufhebung der Schreibsperre
s.Free;
end;
end;
Wichtig sind hier ein paar Punkte, als erstes der Ressourcen-Schutzblock. Dieser ist nötig, da hier ein fmShareDenyWrite (hoffe es heißt so, einfach in der
OH nachschlagen) als Schreibsperre gesetzt wird. Diese Sperre macht Sinn, da keiner Schreiben sollte während Du liest (sonst wäre das was Du liest eventuell total undefiniert). Ressourcen sind immer begrenzt, also sollte man sie auch immer frei geben, egal was passiert.
Der nächste schritt ist die Puffergröße. Ein String ist eigentlich nur ein statisches Array. Schreibst Du s := 'XYZ', dann wird für den String s automatisch Speicher von 3 Byte alloziert und mit den Werten X, Y, Z belegt. Würdest Du jetzt s := s + 'A' schreiben, dann würde an einer anderen Stelle im Speicher neuer Platz von 4 Byte alloziert, die alte Belegung von s dort hinkopiert und an die vierte Stelle ein A geschrieben werden. Hast Du einem String keinen Wert zugewiesen, so belegt dieser auch noch keinen Platz im Speicher. Möchtest Du also wie im Beispiel einen String der Länge 10 lesen, musst Du erst dafür sorgen, dass der String auch 10 Zeichen aufnehmen kann. Das tust Du mit setLength(string, größe).
Als nächstes folgt das Setzen des Positionszeigers. Dies ist immer die Stelle, von der an gelesen oder geschrieben wird. Natürlich solltest Du immer Prüfen, dass die nächste Position > -1 und < s.Size (s der Stream) ist. Alles andere würde die Schranken des Streams verletzen und zu einem Fehler führen. Hier wird der Zeiger (ungeprüft) auf das 10te Zeichen gesetzt. Nun werden von dieser Position (wo auch immer der Zeiger gerade steht) 10 Zeichen in den Puffer gelesen. Der Puffer ist immer die Adresse der ersten Zelle, in die geschrieben wird. Du hättest auch ein Array (beliebigen Typs) oder ein Integer übergeben können. Die Daten werden wirklich roh an diese Stelle im Speicher geschrieben. Ob es sich bei den gelesenen Daten überhaupt um den entsprechenden Typen handelt kann der Stream nicht feststellen, er schiebt einfach die Bytes dort in den Speicher. Auch auf die Grenzen des übergebenen Datums wird der Stream nicht achten. Liest Du 8 Byte in ein Word ein, so wirst Du natürlich die Grenzen des Word (2 Byte) verletzen, für den Stream gibt es aber nur eine Adresse, an die alle 8 Byte geschrieben werden sollen.
Ein Stream erfordert also etwas mehr Kontrolle auf Deiner Seite, ist dafür aber auch sau-schnell. Das liegt daran, dass ein Stream wirklich einfache Speicheroperationen ohne jegliche Prüfung ausführt und nicht mehr (aber auch nicht weniger).
Insbesondere solltest Du immer beachten:
- Den Positionszeiger setzen. Hast Du den auf Position 10 gesetzt und liest 20 Zeichen, steht dieser danach auf 20
- Bei Zuweisung der Position diese prüfen (> -1 und < Stream.Size).
- Vor dem Lesen prüfen ob so viele Bytes im Stream stehen (Stream.Position + BytesToRead < Stream.Size)
- Schauen wieviele Bytes wirklich gelesen/geschrieben wurden (Rückgabewert der Funktion read bzw. write)
- Ressourcenschutzblock verwenden
Sind weniger Regeln als Du jetzt vielleicht denkst und man merkt sie sich schnell! Die sind wirklich einfach und die Arbeit mit Streams sehr angenehm (gerade bei großen Dateien).
Ja, ich hoffe ich konnte Dir grob weiterhelfen. Genaueres kann ich leider nicht sagen, da mir nicht klar ist, welche Stelle Du gerade ändern/betrachten möchtest. Sowohl Zeichen 9 als auch Zeile 9 sind hier keine Werte, die wie eine Zahl wirken. Zudem wäre es wichtig, ob Du hier den String ändern oder ein bestimmtes Byte setzen möchtest. Du kannst immerhin die Zahl 1 als String 1 (ordinaler Wert = 49) oder als Integer (0x00 0x00 0x00 0x01) abspeichern (0xAB = Hex-Darstellung des Bytes, Byte FF = 255, Byte 00 = 0).
Gruß Der Unwissende