[allgemeinen Dateizugriff] EOutOfMemory: StringList.SaveToFile schlägt mit UTF8 fehl
spacer
Autor Nachricht
AScomp
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 133


Delphi 5 Prof., Delphi 7 Prof., Delphi 2007, Delphi 2009
BeitragVerfasst: Mi 07.09.11 12:50 
Betrifft: allgemeinen Dateizugriff
Hallo,

folgendes Problem macht mir derzeit zu Schaffen, vielleicht weiß jemand Rat.

Ich möchte den Inhalt einer TStringList (kann mehrere MB an Daten enthalten) in eine Datei mit UTF8-Codierung schreiben, damit Unicode-Zeichen korrekt gespeichert werden. Dabei kann es zu einem EOutOfMemory-Fehler in der Zeile "fProtList.SaveToFile(AFilename, TEncoding.UTF8);" kommen:

ausblenden Delphi-Quelltext markieren
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
function SaveLogData(const AFilename: String; var LogData: TLogData): Boolean;
var
fProtList: TStringList;
xInt: Integer;
begin
result := false;
if LogData = nil then
exit;
QuickSort(LogData);
try
fProtList := TStringList.Create;
for xInt := Low(LogData) to High(LogData) do begin
fProtList.Add(LogData[xInt].szPath);
fProtList.Add(IntToStr(LogData[xInt].lTime));
end;
fProtList.SaveToFile(AFilename, TEncoding.UTF8);
result := true;
finally
fProtList.Free;
end;
end;


MadExcept:

exception class : EOutOfMemory
exception message : Zu wenig Arbeitsspeicher.
main thread ($96c):
0040773f +0013 bkmaker.exe System 2851 +0 @NewUnicodeString
0040796b +000b bkmaker.exe System 2851 +0 @UStrFromPWCharLen
004a6075 +0091 bkmaker.exe Classes TStrings.GetTextStr
004a678a +002e bkmaker.exe Classes TStrings.SaveToStream
004a6723 +0037 bkmaker.exe Classes TStrings.SaveToFile
008bd227 +009b bkmaker.exe UnitMain 580 +11 SaveLogData

Hat jemand eine Idee, woran das liegen könnte?

Herzlichen Dank und viele Grüße

Andy
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
Werbung ausblenden? Dann registriere Dich kostenlos. Weitere Gründe für eine Registrierung.


Werbung ausblenden? Dann registriere Dich kostenlos. Weitere Gründe für eine Registrierung.
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Moderator
Beiträge: 8118
Erhaltene Danke: 251

Win XP, Win 7
D7 PE, RAD Studio 2009 Professional
BeitragVerfasst: Mi 07.09.11 13:06 
Kommt der Fehler beim Speichern, oder schon während des Einfügens? Probier mal, vor der Schleife die Kapazität der Liste passend zu setzen, also auf
ausblenden Delphi-Quelltext markieren
1:
fProtList.Capacity := 2 * (High(LogData) - Low(LogData) + 1);

Dann muss nur einmal ein zusammenhängender Speicherblock für das Array hinter der Liste reserviert werden, und nicht ständig ein neuer, größerer.

_________________
Oel ngati kameie.
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starhalf offtopic star
Administrator
Beiträge: 8371
Erhaltene Danke: 244

W2k, WXPpro
TP3 - D7pro
BeitragVerfasst: Mi 07.09.11 13:08 
Moin!

user profile iconAScomp hat folgendes geschrieben Zum zitierten Posting springen:
Hat jemand eine Idee, woran das liegen könnte?
Du hast zu wenig RAM? :zwinker: Spaß bei Seite. ;) Du hast bei diesem Ansatz die Daten drei mal im Speicher: :idea:
  1. LogData: TLogData
  2. fProtList: TStringList;
  3. 004a6075 +0091 bkmaker.exe Classes TStrings.GetTextStr
Weiterhin hast du das Erzeugen der StringListe im try-Block, das ist ein Fehler. Das Erzeugen des Objektes liefert im Fehlerfall eine Exception, aber es wird kein Objekt angelegt, also kannst du es im finally auch nicht wieder freigeben. Wenn du das Erzeugen auch kapseln willst, musst du noch ein weiteres try-except drum rum spendieren. :nixweiss:

Ansatz zur Lösung:
Statt eine Stringliste zu erstellen könntest du direkt einen TFileStream aufmachen und die LogData-Elemente (Array?) über einen temporären UTF8-String da rein schreiben. Das sollte Speicher sparen.

//EDIT:
user profile iconGausi hat folgendes geschrieben Zum zitierten Posting springen:
Dann muss nur einmal ein zusammenhängender Speicherblock für das Array hinter der Liste reserviert werden, und nicht ständig ein neuer, größerer.
Interessanter Ansatz, aber wird das Problem nicht lösen, vermute ich. ;) Grund: Der Verwaltungsteil einer Stringliste ist auch nur ein Array aus Doppel-Pointern, die Strings selbst liegen ja auf dem Heap. Wenn man die Stringliste in der Kapazität anpasst, wird nur der Pointer-Block neu alloziert, und das sollte nicht so krasse Effekte haben, zumal die Stringliste intern nicht 1-er Schritte beim Vergrößern macht. :idea:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
AScomp Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 133


Delphi 5 Prof., Delphi 7 Prof., Delphi 2007, Delphi 2009
BeitragVerfasst: Mi 07.09.11 13:45 
Danke euch, wie von Narses beschrieben werde ich es probieren.

try..finally: Stimmt, TStringList.Create steht nicht im try-Block. Ich ging einfach davon aus, dass das Erstellen einer StringList prinzipiell immer funktioniert - aber natürlich kann selbst das schon scheitern.
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
AScomp Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 133


Delphi 5 Prof., Delphi 7 Prof., Delphi 2007, Delphi 2009
BeitragVerfasst: Mi 07.09.11 15:23 
Hätte da gleich nochmal eine Frage.

Und zwar bin ich auf den TStreamWriter gestoßen, mit dem kann ich die Daten wunderschön und recht flott ohne StringList direkt in eine Datei schreiben mit UTF-8-Codierung:

ausblenden Delphi-Quelltext markieren
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
function SaveLogData(const AFilename: String; var LogData: TLogData): Boolean;
var
Writer: TStreamWriter;
xInt: Integer;
begin
result := false;
if LogData = nil then
exit;
QuickSort(LogData);

Writer := TStreamWriter.Create(AFilename, false, TEncoding.UTF8);
try
for xInt := Low(LogData) to High(LogData) do begin
Writer.WriteLine(LogData[xInt].szPath);
Writer.WriteLine(IntToStr(LogData[xInt].lTime));
end;
result := true;
finally
Writer.Free;
end;
end;


Allerdings krieg ich das Einlesen nicht hin:

ausblenden Delphi-Quelltext markieren
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
function LoadLogData(const AFilename: String; var LogData: TLogData): Boolean;
var
Reader: TStreamReader;
szPath, lTime: String;
xLength: Integer;
begin
result := false;
xLength := 0;

if FileExists(AFilename) then begin
Reader := TStreamReader.Create(AFilename, TEncoding.UTF8);
try
while not Reader.EndOfStream do begin
SetLength(LogData, xLength);
LogData[xLength].szPath := Reader.ReadLine;
LogData[xLength].lTime := StrToInt(Reader.ReadLine);
Inc(xLength);
end;
result := true;
finally
Reader.Free;
end;
end;
end;


Nach ein paar Aufrufen von SetLength in der while-Schleife kommt eine Zugriffsverletzung. Ich vermute, dass er Probleme damit hat, das Array ständig zu vergrößern. Da ich allerdings Abwärtskompatibilität benötige, kann ich nicht einfach den Count als erste Zeile in die Datei schreiben und somit SetLength nur einmal aufrufen (was fehlerfrei funktionieren würde, bereits getestet).

Hat mir dazu noch jemand einen Tipp?

Gruß

Andy
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 15840
Erhaltene Danke: 741

XP, W7 x64 (Chrome, IE9, FF), Debian, (OSX 10.7)
RAD XE 2, Java (NB), C++, C# (VS 2010), JS/HTML, PHP, Lazarus
BeitragVerfasst: Mi 07.09.11 15:59 
Setze die Größe nicht in Einzelschritten sondern z.B. immer um 100 hoch, je nach erwarteter Datenmenge. Das lässt sich ja an der Dateigröße abschätzen. Nach Möglichkeit sollte die Schätzung natürlich so sein, dass die Länge genau etwas höher oder gleich der realen Anzahl ist.

Auf die Weise reservierst du seltener neuen Speicher. Und am Ende setzt du die Länge dann auf die reale Größe.
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
AScomp Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 133


Delphi 5 Prof., Delphi 7 Prof., Delphi 2007, Delphi 2009
BeitragVerfasst: Mi 07.09.11 16:08 
Das hatte ich jetzt testhalber ohnehin schon gemacht. Allerdings hatte ich gehofft, dass es noch eine elegantere Lösung gibt.

Hast du noch eine Idee, weshalb es beim ständigen Vergrößern des Arrays zu Zugriffsverletzungen kommt? Gibt es dafür eine plausible Erklärung oder ist es einfach eine zu häufige Speicherreservierung in zu kurzen Zeitabständen?
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Moderator
Beiträge: 8118
Erhaltene Danke: 251

Win XP, Win 7
D7 PE, RAD Studio 2009 Professional
BeitragVerfasst: Mi 07.09.11 16:13 
Vielleicht solltest du auch zu Beginn die Länge auf 1 setzen, nicht auf 0. Ein Array der Länge 0 hat nämlich gar keinen Eintrag, auch nicht den Nullten.

Du schreibst also immer neben dein Array - das geht wohl eine Zeitlang gut, aber irgendwann knallts halt. ;-)

_________________
Oel ngati kameie.
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starhalf offtopic star
Administrator
Beiträge: 8371
Erhaltene Danke: 244

W2k, WXPpro
TP3 - D7pro
BeitragVerfasst: Mi 07.09.11 16:13 
Moin!

user profile iconAScomp hat folgendes geschrieben Zum zitierten Posting springen:
Allerdings hatte ich gehofft, dass es noch eine elegantere Lösung gibt.
Statt Array eine Linked-List nehmen? :nixweiss:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
AScomp Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 133


Delphi 5 Prof., Delphi 7 Prof., Delphi 2007, Delphi 2009
BeitragVerfasst: Mi 07.09.11 16:30 
Danke Gausi, das war's!

-> SetLength(LogData, xLength + 1);

Manchmal sieht man den Wald vor lauter Bäumen nicht. Oder man verwechselt Index mit Count. ;-)
 
Antworten mit Zitat Beitrag melden
Private Nachricht sendenPosting in privater Nachricht zitieren
home home