![]() |
Textdatei zum Lesen & Schreiben öffnen
Hi,
Ich möchte eine TextDatei öffnen. Mein Ideal wäre folgendes: - Öffnen zum lesen und schreiben gleichzeitig - Zeilenweise auslesen/reinschreiben (wichtig!) - An bestimmte Zeile springen (Seek o.ä. für Zeilen) - Einfügen in datei (wäre das nonplusultra^^) Was ich nicht brauche ist byteweiser Zugriff... Was nehme ich da am besten? öffnen und schreiben gleichzeitig fällt textfile raus zeilenweise fällt FileStream raus was könnt man da noch nehmen? |
Re: Textdatei zum Lesen & Schreiben öffnen
TStringList, damit wäre auch Einfügen/Entfernen gelöst.
Abgesehn von #13#10, #13, #10 und den anderen "Zeilenvorschüben" wird alles Byte für Byte (unverändert) verwaltet. Und ein Byteweiser Zugriff wäre auch realisierbar ... entweder Stückchenweise/Zeilenweise, oder mit der Text-Eigenschaft über alles. am Anfang alles per LoadFromFile da reinladen, dann wild drin rumlesen/-ändern und zum Schluß mit SaveToFile alles zurück in die Datei |
Re: Textdatei zum Lesen & Schreiben öffnen
oder mein vorschlag denn ich immer wieder erwähne...
ein 2. temporäres textfile erstellen in dem die änderungen gespeichert werden, original löschen, temp in original umbenennen.... |
Re: Textdatei zum Lesen & Schreiben öffnen
Zitat:
|
Re: Textdatei zum Lesen & Schreiben öffnen
Ähm ja, das hatte ich vergessen :wall:
Das ganze soll eine art Buffering werden. d.h. ich will einen Editor haben welcher nicht die gesamte Datei im Speicher hat. Daher kommt es ebenfalls nicht in Frage die gesamte Datei (TStringList) zu laden. |
Re: Textdatei zum Lesen & Schreiben öffnen
Da wird es dann etwas schwieriger, wenn du die Anzahl/Länge der Strings in einem Teilbereich ändern willst.
Also entweder doch mit 2 Dateien und dann per ReadLn in der einen einlesen und per WriteLn in die andere Schreiben ... und dann halt Zeilen zwischenschieben, bzw. unerwünschte Zeilen einfach nicht mehr speichern. - ansonsten mußt du ja die Datei selber nach einem Zeilenwechsel nahe der gewünschten Blockgröße durchsuchen - dann den Bereich einlesen - ändern... - je nach Längenänderung des Blocks die nachfolgenden Daten in der Datei verschieben - den Block speichern - nach diesem Block dann den nächsten Zeilenwechsel suchen - dann den Bereich einlesen ..... |
Re: Textdatei zum Lesen & Schreiben öffnen
Also es sollte so funktionieren:
Code:
Nur: Schieben???
Buffer an Stelle P laden:
Start = ScrollToLineStart(P - BufSize div 2) Stop = ScrollToLineEnd(P + BufSize div 2) Buffer = Copy(Start,Stop) Buffer zurückschreiben: Start = ScrollToLineStart(P - BufSize div 2) Stop = ScrollToLineEnd(P + BufSize div 2) Schieben(Stop,Size(Buffer)-BufSize) //Bytes ab Stelle Stop um x Bytes verschieben Write(Buffer,Start) [Edit] Eine andere Änderung war folgendes: den Text teilweise in Ram laden, aber NICHT ändern. sozusagen bearbeitung im readonly-mode und die änderung werden entsprechend gespeichert...(wie wäre noch die frage) beim speichern würden dann einfach die änderungen nach dem auftreten in der datei sortiert und die datei einmal von vorne nach hinten durchgeackert... oder eine weitere möglichkeit (da ganze soll ein Editor für SEHR große dateien werden) die Datei read-only zu öffnen und bearbeiten nur als überschreiben zuzulassen (=länge konstant) und löschen oder einfügen nur als sonderfunktion ausserhalb des eigentlichen editors... |
Re: Textdatei zum Lesen & Schreiben öffnen
Mir fängt eh grad der Kopf vor Treibern an zu rauchen ...
mal sehn ob ich schnell 'ne kleine Klasse zusammenbekomm. ^^ *Abwechslung brauch* |
Re: Textdatei zum Lesen & Schreiben öffnen
Ich hatte grad schon wieder ne neue Idee :lol:
Eine verkette Liste mit Strings...
Delphi-Quellcode:
jedes repräsentiert eine Zeile...
TLine = class
Next, Prev: TLine; Text: String; Changed: Boolean; //2 Byte? end; Vorteil: man könnte einmal die Datei "analysieren" und sich die Liste aufbauen (erzeugt natürlich Overhead :-( 10 Byte pro Zeile ist leider nicht wenig... ) Nun wird zum Laden einfach mittels eines gemoddeten TFileStreams zur x'ten Zeile gesprungen (x = Pos-(Bufsize div 2)) und mittels FS.ReadLn geladen. Alles was ausserhalb des gebufferten Bereichs liegt und nicht geändert wurde wird geleert (Text:='';) Somit befindet sich immer nur der Overhead, die gebufferten Zeilen und die geänderten Zeilen im Speicher. Ich denke deutlich besser geht es nicht^^ Beispielrechung (Das Beispiel ist eine (der kleineren) Datei die ich vorliegen hab, also durchaus realistisch ;-) ): 2 Mio Zeilen ca. 50 Zeichen/Zeile 1000 Zeilen editiert Buffer: 100000 Zeilen Dateigröße: 2Mio * 50 = 100MB Overhead: 10*2Mio = 20MB Buffer: 100000*50 = 5MB Geändert: 1000*50 = 50KB (Die höchstwahrscheinlich schon im Buffer sind) Das ganze ist eigentlich genial: Einfügen von Zeilen ist dank der verketteten Liste ein Kinderspiel, einfügen von Buchstaben kann in handlichen Stücken (in der Zeile) erfolgen. Löschen ist ebenfalls kein Problem. Einziges Problem ist das Speichern, aber auch das sollte gar nicht so schwer sein...Mein Ansatz: Speichern in eine neue Datei, alte Datei löschen, neue umbenennen, fertig. ;-) Also Zeile für Zeile durchgehen, falls was im Buffer steht das nehmen, ansonsten aus der alten Datei lesen. Event. sogar das 2-Dateien-System an den User weitergeben und mit einer "Quelldatei" und einer "Arbeitsdatei" basteln lassen. Nun, was natürlich stört ist die Dateigröße... event. wäre es ja möglich die liste nur so groß zu machen wie der Buffer ist und somit den overhead auf ein minimum beschränken... allerdings ist dann das mit dem ändern nicht mehr so schön einfach :-/ ideen? |
Re: Textdatei zum Lesen & Schreiben öffnen
OK, das wäre och noch 'ne Möglichkeit ... hast'e da inzwischen schon was zusammen?
hier mal meine Klasse. - ist bisher aber noch nicht getestet worden (nur im Kopf entstanden ... ich hoff mal da sind keine großen Denkfehler drin) - aber der Compiler meckert schonmal nicht :stupid: Overhead wäre hierbei "nur" der aktuell geladene Block, allerdings würde natürlich beim Speichern eines Blockes eventuell der gesamte nachfolgende Teil verschoben werden.
Delphi-Quellcode:
.
Type TPartialTextfile = Class Private _FileName: String; _FileHandle: THandle; _FileSize, _Start: Int64; _OrgLen: Integer; _Part: TStringList; Function _FindNextLineBreak(Const i: Int64): Integer; Public Constructor Create; Destructor Destroy; Override; Property FileName: String Read _FileName; Property FileSize: Int64 Read _FileSize; Property Start: Int64 Read _Start; Property OrgLen: Integer Read _OrgLen; Property Part: TStringList Read _Part; Function Open (Const FileName: String): Boolean; Function LoadPart (Const Pos: Int64; MinLen: Integer): Boolean; Overload; Function LoadPart ( {next part} MinLen: Integer): Boolean; Overload; Function LoadPartLi(Const Pos: Int64; Lines: Integer): Boolean; Overload; Function LoadPartLi( {next part} Lines: Integer): Boolean; Overload; Function SavePart: Boolean; Procedure Close; End; Function TPartialTextfile._FindNextLineBreak(Const i: Int64): Integer; Var B: Array[0..65535] of Char; i2: LARGE_INTEGER; W, W2: Cardinal; Begin Result := 0; Repeat i2.QuadPart := i + Result; i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN); If i2.QuadPart <> i + Result Then Exit; ReadFile(_FileHandle, B, SizeOf(B), W, nil); If (W <> SizeOf(B)) and (W <> _FileSize - i - Result) Then Exit; W2 := 0; While W2 < W do Begin Inc(W2); Case B[W2 - 1] of #0, #10: Break; #13: Begin If W2 > W Then Begin i2.QuadPart := i + Result + W2; i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN); If i2.QuadPart = i + Result + W2 Then Begin ReadFile(_FileHandle, B, 1, W, nil); If (W = 1) and (B[0] = #10) Then Inc(W2); End; End Else If B[W2] = #10 Then Inc(W2); Break; End; End; End; Inc(Result, W2); Until W < SizeOf(B); End; Constructor TPartialTextfile.Create; Begin _FileHandle := INVALID_HANDLE_VALUE; _Part := TStringList.Create; End; Destructor TPartialTextfile.Destroy; Begin _Part.Free; End; Function TPartialTextfile.Open(Const FileName: String): Boolean; Var i64: LARGE_INTEGER; Begin If _FileHandle <> INVALID_HANDLE_VALUE Then Close; _FileHandle := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0); If _FileHandle <> INVALID_HANDLE_VALUE Then Begin i64.LowPart := GetFileSize(_FileHandle, @i64.HighPart); _FileName := FileName; _FileSize := i64.QuadPart; Result := True; End Else Result := False; End; Function TPartialTextfile.LoadPart(Const Pos: Int64; MinLen: Integer): Boolean; Var S: String; W: Cardinal; i: LARGE_INTEGER; Begin Result := False; If (_FileHandle = INVALID_HANDLE_VALUE) or (Pos > _FileSize) or (MinLen <= 0) Then Exit; Try SetLength(S, _FindNextLineBreak(Pos + MinLen)); Except Exit; End; i.QuadPart := Pos; i.LowPart := SetFilePointer(_FileHandle, i.LowPart, @i.HighPart, FILE_BEGIN); If i.QuadPart <> Pos Then Exit; ReadFile(_FileHandle, S[1], Length(S), W, nil); If Length(S) <> Integer(W) Then Exit; Try _Start := Pos; _OrgLen := Length(S); _Part.Text := S; Except _OrgLen := 0; _Part.Clear; Exit; End; Result := True; End; Function TPartialTextfile.LoadPart(MinLen: Integer): Boolean; Begin Result := LoadPartLi(_Start + _OrgLen, MinLen); End; Function TPartialTextfile.LoadPartLi(Const Pos: Int64; Lines: Integer): Boolean; Var S: String; W: Cardinal; i: LARGE_INTEGER; Begin Result := False; If (_FileHandle = INVALID_HANDLE_VALUE) or (Pos > _FileSize) or (Lines <= 0) Then Exit; W := 0; While Lines > 0 do Begin Inc(W, _FindNextLineBreak(Pos + W)); If Integer(W) < 0 Then Exit; Dec(Lines); End; Try SetLength(S, W); Except Exit; End; i.QuadPart := Pos; i.LowPart := SetFilePointer(_FileHandle, i.LowPart, @i.HighPart, FILE_BEGIN); If i.QuadPart <> Pos Then Exit; ReadFile(_FileHandle, S[1], Length(S), W, nil); If Length(S) <> Integer(W) Then Exit; Try _Start := Pos; _OrgLen := Length(S); _Part.Text := S; Except _OrgLen := 0; _Part.Clear; Exit; End; Result := True; End; Function TPartialTextfile.LoadPartLi(Lines: Integer): Boolean; Begin Result := LoadPartLi(_Start + _OrgLen, Lines); End; Function TPartialTextfile.SavePart: Boolean; Var S: String; W, W2: Cardinal; B: Array[0..65535] of Char; i, i2: LARGE_INTEGER; i3: Integer; Begin Result := False; If _FileHandle = INVALID_HANDLE_VALUE Then Exit; Try S := _Part.Text; Except Exit; End; i3 := _OrgLen - Length(S); If i3 > 0 Then Begin i.QuadPart := _Start + _OrgLen; While i.QuadPart < _FileSize do Begin i2.QuadPart := i.QuadPart; i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN); If i2.QuadPart <> i.QuadPart Then Exit; ReadFile(_FileHandle, B, SizeOf(B), W, nil); If (W <> SizeOf(B)) and (W <> _FileSize - i.QuadPart) Then Exit; W2 := W; i2.QuadPart := i.QuadPart - i3; i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN); If i2.QuadPart <> i.QuadPart - i3 Then Exit; WriteFile(_FileHandle, B, W2, W, nil); If W <> W2 Then Exit; Inc(i.QuadPart, SizeOf(B)); End; End Else If i3 < 0 Then Begin i3 := -i3; i.QuadPart := _Start + _OrgLen; Inc(i.QuadPart, (_FileSize - i.QuadPart) and -SizeOf(B)); While i.QuadPart >= _Start + _OrgLen do Begin i2.QuadPart := i.QuadPart; i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN); If i2.QuadPart <> i.QuadPart Then Exit; ReadFile(_FileHandle, B, SizeOf(B), W, nil); If (W <> SizeOf(B)) and (W <> _FileSize - i.QuadPart) Then Exit; W2 := W; i2.QuadPart := i.QuadPart + i3; i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN); If i2.QuadPart <> i.QuadPart + i3 Then Exit; WriteFile(_FileHandle, B, W2, W, nil); If W <> W2 Then Exit; dec(i.QuadPart, SizeOf(B)); End; End; Dec(_FileSize, i3); i.QuadPart := _FileSize; i.LowPart := SetFilePointer(_FileHandle, i.LowPart, @i.HighPart, FILE_BEGIN); If i.QuadPart = _FileSize Then SetEndOfFile(_FileHandle); i.QuadPart := _Start; i.LowPart := SetFilePointer(_FileHandle, i.LowPart, @i.HighPart, FILE_BEGIN); If i.QuadPart <> _Start Then Exit; WriteFile(_FileHandle, S[1], Length(S), W, nil); _OrgLen := Length(S); Result := Length(S) <> Integer(W); End; Procedure TPartialTextfile.Close; Begin CloseHandle(_FileHandle); _FileName := ''; _FileHandle := INVALID_HANDLE_VALUE; _FileSize := 0; _Start := 0; _OrgLen := 0; _Part.Clear; End;
Delphi-Quellcode:
[edit]
// Edit: TEdit;
// Button1: TButton; Procedure TForm1.Button1Click(Sender: TObject); Var PT: TPartialTextfile; Begin PT := TPartialTextfile.Create; PT.Open('Unit1.pas'); Memo1.Lines.Clear; Repeat PT.LoadPartLi(10); Memo1.Lines.Add(Format('*** %d, %d/%d', [PT.Start, PT.OrgLen, Length(PT.Part.Text)])); Memo1.lines.AddStrings(PT.Part); Memo1.Lines.Add(''); Until PT.Part.Text = ''; PT.Free; End; Fehler im Code behoben das Auslesen funktioniert anscheinend und auch eine kleine Testprozedur ist nun mit dabei [add] wenn du die geänderten Strings erstmal hinter dem "originalem" Dateiende in der Datei speicherst, dann würde auch einer dieser beiden Varianten für deine Idee ausreichen ... außerdem würden die dann nicht im RAM liegen, was bei vielen Änderungen in großen Dateien besser wäre.
Delphi-Quellcode:
PLine = ^TLine;
TLine = packed record Next, Prev: PLine; TextPos: Int64; TextLen: Integer; end; TLineArray = Array of Record TextPos: Int64; TextLen: Integer; end; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:07 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz