PDA

Ver la Versión Completa : out of memory usando TStringList


Anel Hernandez
18-11-2022, 06:21:37
hola,


Debo leer 8784 ficheros de texto con 3600 lineas con datos numericos cada uno. De cada uno de ellos debo seleccionar un dato x cada linea, y convertirlos a una fila con los 3600 datos copiados. Y luego con cada linea hacer un fichero final con 366 lineas, cada una con 3600 datos numericos.
Leo sin problema los 8784 ficheros iniciales, los organizo en un stringgrid para ir monitoriando que todo esta ok y luego los adiciono a un stringlist para salvarlo a un nuevo fichero con todos los datos organizados.
La tabla lleva 3 filas encima con encabezados de tabla que se incluyen tambien en el fichero final.
Funciona correctamente hasta el fichero 6378 que crea un fichero nuevo de 240 MB.
Pero cuando intento añadir mas ficheros da error "out of memory".
Les dejo mi codigo para que me digan que pudiera hacer diferente.


procedure lector;
var
i,j:integer;
milista,listafinal:TStringList;
s:string;
begin
milista:=TStringList.Create;
listafinal:=TStringList.Create;
for i := 0 to form1.CheckListBox1.Count-1 do begin
Application.ProcessMessages;
if form1.CheckListBox1.Checked[i] then begin
milista.LoadFromFile(form1.CheckListBox1.Items[i]);
form1.stringgrid7.Cells[0,i+3]:=inttostr(i+1);
s:=ExtractFileName(form1.checklistbox1.items.Strings[i]);
form1.stringgrid7.Cells[1,i+3]:=copy(s,0,4);
form1.stringgrid7.Cells[2,i+3]:=inttostr(Juliana(copy(s,0,13)));
form1.stringgrid7.Cells[3,i+3]:=copy(s,14,4);
form1.stringgrid7.rowCount:=i+4;
for j:=4 to milista.Count-3 do begin
if i=0 then begin // pongo los encabezados de tabla
form1.stringgrid7.colCount:=j+1;
form1.stringgrid7.Cells[j,0]:=inttostr(j-3-numX*trunc((j-4)/numX));
form1.stringgrid7.Cells[j,1]:=inttostr( trunc((j-4)/numY)+1 );
form1.stringgrid7.Cells[j,2]:=inttostr(j-3);
end;
form1.stringgrid7.Cells[j,i+3]:=copy(milista.Strings[j+2],28,10);
end;

form1.stringgrid7.Rows[i+3].Delimiter:=' ';
listafinal.Add(form1.stringgrid7.Rows[i+3].DelimitedText);

end;
milista.Clear;
end;
milista.Free;
listafinal.SaveToFile(dirE+'final.txt'); //aqui es donde da el error
listafinal.Free;
end;

Gracias,
A

duilioisola
18-11-2022, 08:58:09
Yo trataría de escribir en el fichero destino directamente.
La forma más simple es AssignFile()..Rewrite()..Write()/WriteLn..CloseFile().

Además de esto, recuerda siempre utilizar los bloques try..finally y try..except.


procedure lector;
var
i, j : integer;
milista : TStringList;
// listafinal : TStringList;
s : string;
F : TextFile;
FileName : string;
begin
// listafinal := TStringList.Create;
// Abro el fichero destino
FileName := dirE + 'final.txt';
AssignFile(F, FileName);
try
// Si existe lo reescribo
Rewrite(F);

milista := TStringList.Create;
try
for i := 0 to form1.CheckListBox1.Count-1 do begin
Application.ProcessMessages;

if form1.CheckListBox1.Checked[i] then begin
milista.LoadFromFile(form1.CheckListBox1.Items[i]);

form1.stringgrid7.Cells[0, i+3] := inttostr(i+1);
s := ExtractFileName(form1.checklistbox1.items.Strings[i]);
form1.stringgrid7.Cells[1, i+3] := copy(s, 0, 4);
form1.stringgrid7.Cells[2, i+3] := inttostr(Juliana(copy(s, 0, 13)));
form1.stringgrid7.Cells[3, i+3] := copy(s, 14, 4);
form1.stringgrid7.rowCount := i+4;

for j := 4 to milista.Count-3 do begin
if i=0 then begin // pongo los encabezados de tabla
form1.stringgrid7.colCount := j+1;
form1.stringgrid7.Cells[j, 0] := inttostr(j-3-numX*trunc((j-4)/numX));
form1.stringgrid7.Cells[j, 1] := inttostr( trunc((j-4)/numY)+1 );
form1.stringgrid7.Cells[j, 2] := inttostr(j-3);
end;

form1.stringgrid7.Cells[j, i+3] := copy(milista.Strings[j+2], 28, 10);
end;

form1.stringgrid7.Rows[i+3].Delimiter := ' ';
// listafinal.Add(form1.stringgrid7.Rows[i+3].DelimitedText);
// Escribo la linea en el fichero destino
WriteLn(F, form1.stringgrid7.Rows[i+3].DelimitedText);
end;

milista.Clear;
end;
finally
milista.Free;
end;

// listafinal.SaveToFile(dirE+'final.txt');
// Cierro fichero destino
CloseFile(F);
except
on e: Exception do
ShowMessage('Error al crear fichero : ' + FileName + #13#10 + e.Message);
end;

// listafinal.Free;
end;

marco3k
18-11-2022, 22:01:37
Un objeto de la clase TStringList es útil para procesar listas de cadenas de texto en memoria(ram). Asi como menciona duilioisola, mejor escribe directamente al disco con las funciones nativas de texto. O si quieres aun consolidar en un archivo puedes organizar todo en un clientdataset desconectado (esto se almacena directamente al disco, no en la ram) y despues escribirlo al disco, pero mejor graba directamente al disco es mas rápido así.

Anel Hernandez
18-11-2022, 22:41:57
Gracias*duilioisola,

De que depende el tamaño en la ram en el que puedo almacenar mi stringlist? Como puedo saber ese tamaño?

Gracias marco3k,

Anel Hernandez
20-11-2022, 14:10:11
Hola,

finalmente segui el consejo de duilioisola y sige dando error "out of memory". Pero

esta vez llega hasta el fichero 8774 y crea un fichero de 330 MB.

Decidi separar los procesos en botones separados, uno para la lectura de los

ficheros iniciales y puesta en tabla. Mientras, el segundo boton salvaba toda la

tabla en un fichero con las instrucciones de duilioisola.

Esta vez funciono OK.

Que puede estar pasando que da error cuando lo hago combinado como el ejemplo de

duilioisola y sin embargo cuando lo hago separado funciona sin problema?

Gracias,
A

duilioisola
21-11-2022, 11:10:01
Veo que en medio del cálculo y escritura al fichero vas rellenando un TStringGrid (form1.stringgrid7).
recorres 8784 ficheros
1 Agregas 4 filas con con las 4 primeras columnas rellenadas con datos que lees.
2 Agregas tantas columnas como filas tenga el fichero
3 Escribes la tercera línea insertada al fichero de texto.

Esto genera un StringGrid enorme (8784 lineas x Tantas columnas como el fichero más grande leido(3600)).
8.784 x 3.600 = 31.622.400 celdas
Parece que todos contienen número convertidos a texto (aparentemente de 4 dígitos de máximo.)
Sin contar datos para la estructura (punteros, RTTI, y otros datos de control de StringGrid) tienes en memoria 126.489.600 bytes

Si no me he equivocado en las cuentas tienes 126 MB de memoria ocupada por el StringGrid.

Dado que no creo que estés mostrando el progreso en pantalla mediante el StringGrid, supongo que lo mas sensato es no utilizarlo y hacer algo mucho mas simple
He comentado el codigo a reemplazar, pero he dejado la parte donde rellenas las primeras 4 columnas del StringGrid con datos del fichero importado.
8.784 x 4 x 4bytes = 140.544 bytes (140 KB)


procedure lector;
var
i, j : integer;
milista : TStringList;
// listafinal : TStringList;
s, aux : string;
F : TextFile;
FileName : string;
begin
// listafinal := TStringList.Create;
// Abro el fichero destino
FileName := dirE + 'final.txt';
AssignFile(F, FileName);
try
// Si existe lo reescribo
Rewrite(F);

milista := TStringList.Create;
try
for i := 0 to form1.CheckListBox1.Count-1 do begin
Application.ProcessMessages;

if form1.CheckListBox1.Checked[i] then begin
milista.LoadFromFile(form1.CheckListBox1.Items[i]);

form1.stringgrid7.Cells[0, i+3] := inttostr(i+1);
s := ExtractFileName(form1.checklistbox1.items.Strings[i]);
form1.stringgrid7.Cells[1, i+3] := copy(s, 0, 4);
form1.stringgrid7.Cells[2, i+3] := inttostr(Juliana(copy(s, 0, 13)));
form1.stringgrid7.Cells[3, i+3] := copy(s, 14, 4);
form1.stringgrid7.rowCount := i+4;

{
for j := 4 to milista.Count-3 do begin
if i=0 then begin // pongo los encabezados de tabla
form1.stringgrid7.colCount := j+1;
form1.stringgrid7.Cells[j, 0] := inttostr(j-3-numX*trunc((j-4)/numX));
form1.stringgrid7.Cells[j, 1] := inttostr( trunc((j-4)/numY)+1 );
form1.stringgrid7.Cells[j, 2] := inttostr(j-3);
end;

form1.stringgrid7.Cells[j, i+3] := copy(milista.Strings[j+2], 28, 10);
end;

form1.stringgrid7.Rows[i+3].Delimiter := ' ';
// listafinal.Add(form1.stringgrid7.Rows[i+3].DelimitedText);
// Escribo la linea en el fichero destino
WriteLn(F, form1.stringgrid7.Rows[i+3].DelimitedText);
}
// Primer registro
j := 4;
aux := copy(milista.Strings[j+2], 28, 10);
// Siguientes registros separados por ' '
for j := 5 to milista.Count-3 do begin
aux := aux + ' ' + copy(milista.Strings[j+2], 28, 10);
end;
// Escribo los valores separados por espacios
WriteLn(F, aux);
end;

milista.Clear;
end;
finally
milista.Free;
end;

// listafinal.SaveToFile(dirE+'final.txt');
// Cierro fichero destino
CloseFile(F);
except
on e: Exception do
ShowMessage('Error al crear fichero : ' + FileName + #13#10 + e.Message);
end;

// listafinal.Free;
end;

marco3k
21-11-2022, 17:32:54
Hola,

finalmente segui el consejo de duilioisola y sige dando error "out of memory". Pero

esta vez llega hasta el fichero 8774 y crea un fichero de 330 MB.

Decidi separar los procesos en botones separados, uno para la lectura de los

ficheros iniciales y puesta en tabla. Mientras, el segundo boton salvaba toda la

tabla en un fichero con las instrucciones de duilioisola.

Esta vez funciono OK.

Que puede estar pasando que da error cuando lo hago combinado como el ejemplo de

duilioisola y sin embargo cuando lo hago separado funciona sin problema?

Gracias,
A


Bueno tu proceso maneja basantes datos y tanto el stringgrid y stringlist almacenan los datos en memoria ram y tu al mencionar que generas archivos de salida de mas de 300 mb entonces es evidente que te salga ese mensaje de error. Para hacer todo junto podrias probar lo que te comente al inicio, que es consolidar tus datos en un clientdataset desconectado y a la par escribir en los archivos de texto. En ambos procesos estas escribiendo directamente al disco, técnicamente no tendria porque colgarse (porque no vas a usar intensivamente la ram), pero habria q evaluar la velocidad del proceso si conviene hacerlo asi, como optimizar al clientdataset anulando el logchange a falso y usar las columnas directamente (no usar fieldbyname) ya que la ram es mas rapida. A lo mejor haciendo separado es mas rapido (consolidar en stringgrid y luego grabar a txt), eso lo tendrías que probar.