Jump to content


Photo

¿Como se guarda y se lee en un buffer para trabajar con archivos?

Buffers archivos

  • Please log in to reply
10 replies to this topic

#1 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 06 April 2015 - 02:38 PM

Si bien tiene relación con lo que estuve analizando en otro hilo. En esta ocasión me pregunto en como debería proceder a la lectura y almacenado de un archivo mediante buffers.

 

De lo que estuve analizando sobre las diferentes opciones de como leer un archivo, lo más apropiado cuando se trata de archivos grandes (de 10+ MB) es leerlo por "bloques" y procesarlo.

 

Hasta el momento no he tocado nunca esta técnica. Con el común STringList o sus "parientes" me era suficiente para manejar unos cuantos miles de líneas. Esta vez, me planteo cosas más complejas y requerien de mejores opciones.

 

Necesito leer y almacenar vectores y matrices de grandes dimensiones. El primer punto: estos son dinámicos. Y no hay un tamaño fijo.

 

Mi prueba de rendimiento, para ver el peor caso, inicial consistió en generar un archivo para una matriz de un poco más de 10mil x 10mil. Si bien no sospecho llegar a tanto... si seguro que necesito tamaños del orden de las 6 a 7 cifras (cuanto mucho) pero necesito que esto sea abierto. La técnica empleada es la usanza de la vieja escuela y el simple write/writeln. Naturalmente el archivo superó los 2GB y hasta mi Hulk-PC se tomó el tiempito de generarlo. :shocked:

 

En vista a que no he tocado en mi vida la generación de archivos por otra forma, me sueltan dudas de como debo proceder a leer y/o escribir en el buffer.

 

Hasta donde se, tal buffer se debe declarar de un tamaño fijo y de tipo byte (o char) como por ejemplo:


delphi
  1. Buffer = array[0..1023] of Byte;

Pero en vista a que mis estructuras son:


delphi
  1. type
  2. TVector = array of Double;
  3. TMatrix = array of array of Double;

¿Cómo le hago para almacenar y leer desde este buffer considerando que la info que pretendo almacenar sea del tipo Double?

 

Mi máxima meta es tener un archivo que tenga la siguiente estructura:

Rows:<CantFilas>

Cols:<CantColumnas>

Orientation:<moRow|moCol> // que sirve para indicar si se trata de una matriz de filas o una matriz de columnas

Data:

1.23456790E+308

....

 

 

Es decir que tuviera una especie de un archivo ini a modo de header que tenga la info de las dimensiones y la orientación. Esto me sirve para determinar la forma de proceder al "indexado". El algoritmo de como convertir una coordenada [i,j] a Idx (posición en el archivo) y viceversa lo tengo.

Para los vectores es similar, como estos no tienen una 2da dimensión solo hace falta una y tampoco es estrictamente necesario tener la orientación.

El tener el Header con las dimensiones también me permitiría comprobar si efectivamente la cantidad de "items" se corresponden.

 

Mi prueba y versión inicial del archivo no tenía este header. ¿Como le podría incorporar esto?

 

La otra duda que tengo, es si ante los cambios en los datos (por ciertos cálculos) de mis estructuras me sería conveniente generar un nuevo archivo de cero, o si es viable la posibilidad de sobreescribirlo sin problemas. Si hay cambios no los será en su dimensión pero si en el contenido.

 

¿Alguna orientación?

 

Saludos,


  • 0

#2 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 06 April 2015 - 05:23 PM

No termino de comprender que es exactamente, o mejor, como quieres guardar tus datos. No te sirven las líneas de texto, a no ser que sea texto lo que deseas guardar.

 

El tema de guardar una estructura cabecera, es sencillo. Si la estructura tiene tamaño binario fijo, solo tienes que leer en tu buffer ese número de bites para luego hacer una conversión del buffer a tu estructura mediante un simple cast de tipos de punteros y ya tienes un puntero a tu estructura con los tipos que vas a usar. Para leer cualquier tipo de dato en un buffer de BYTES basta que hagas un cast de un tipo de puntero a otro.

 

No se si es lo que andas buscando.

 

 

Saludos.


  • 0

#3 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 06 April 2015 - 05:49 PM

No termino de comprender que es exactamente, o mejor, como quieres guardar tus datos. No te sirven las líneas de texto, a no ser que sea texto lo que deseas guardar.

 

El tema de guardar una estructura cabecera, es sencillo. Si la estructura tiene tamaño binario fijo, solo tienes que leer en tu buffer ese número de bites para luego hacer una conversión del buffer a tu estructura mediante un simple cast de tipos de punteros y ya tienes un puntero a tu estructura con los tipos que vas a usar. Para leer cualquier tipo de dato en un buffer de BYTES basta que hagas un cast de un tipo de puntero a otro.

 

No se si es lo que andas buscando.

 

 

Saludos.

 

Hola escafandra. Gracias por colaborar.

 

Básicamente lo que pretendo es leer una enorme cantidad de datos de tipo double desde un archivo hacia una matriz y/o vector. La fácil es que la técnica de vieja escuela, armo un archivo de tipo definido con double y empiezo a leer y escribir. Esto no es muy rápido si tengo miles de datos por operar. En el archivo al final tengo un listado con números como esto:

 

1.2345689E+100

3.4223235E+308

...

 

Una alternativa es leer el archivo por "bloques" o a "partes" mediante un buffer. El asunto, y que no he encontrado documentación que me aclare estas cuestiones, es como proceder a leer desde este buffer el listado. Todos los ejemplos que he visto tratan con un buffer de tipo byte o bien char.

Mi duda es como proceder a trabajar con esta técnica.

 

Digamos que yo empleo un buffer de unos 1024 bytes, y leyera del archivo esa cantidad. En el buffer tengo bytes que en teoría son datos que debo interpretar como double. La teoría dice que en 1024 bytes hay 128 números del tipo double (1 double = 8 bytes). ¿Pero cómo los leo? ¿Cómo le indico que en ese buffer desde la posición x a la y corresponde a un byte que luego pondré en un (i,j) de mi matriz?. Y a su vez al momento de guardar en el archivo por medio del buffer ¿Cómo lo encaro?

 

A mi lo que más me gustaría poder encarar es un archivo que tuviera una estructura como la que describí al inicio. Una especie de cabecera que contenga las dimensiones y la orientación y luego los datos. De esta forma puedo almacenar tanto los datos como así también la descripción del archivo y poder asegurarme de que la cantidad de datos se corresponda con la dimensión leída.

 

Esto implicaría trabajar con un tipo de archivo "definido por el usuario". Y al combinarlo por lectura/escritura basada en Buffer a provocado cortos en mi cabeza. 10.gif

 

Se que está la clase TFileStream, y otras análogas que me pueden ayudar a hacer el trabajo. Lo que no le entiendo es como usar el buffer.

 

¿Me explico?


  • 0

#4 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 07 April 2015 - 05:00 AM

La estructura de tu cabecera debería ser algo como:


delphi
  1. TCabecera = packed record
  2.   Rows: DWORD;
  3.   Cols: DWORD;
  4.   Orientation: DWORD;
  5.   Data: array [0..0] of double;
  6. end;
  7. PTCabecera =  ^TCabecera;

Tambien definiríamos el Buffer como:


delphi
  1. TBuffer = array [0..1023] of char;
  2. PTBuffer = ^TBuffer;

Supongamos que hacemos una primera lectura del principio del archivo en el buffer. La conversión de ese biffer, de esta forma:


delphi
  1. var
  2. Cabecera: PTCabecera;
  3. begin
  4. Cabecera:= PTCabecera(@Buffer[0]);

Nos da la cabecera buscada y el primer elemento del array Data. Todo lo siguiente son más elementos de Data.

 

El tamaño del buffer debe ser mínimo el de la estructura cabecera y un múlltiplo de sizeof(double). al poner packet nos aseguramos que el compilador respetará el tamaño real de nustra estructura, que es fundamental para hacer bien la conversión del buffer a cabecera.

 

No se si la idea va por el camino que buscas.

 

 

Saludos.


  • 0

#5 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 07 April 2015 - 06:11 AM

La estructura de tu cabecera debería ser algo como:


delphi
  1. TCabecera = packed record
  2.   Rows: DWORD;
  3.   Cols: DWORD;
  4.   Orientation: DWORD;
  5.   Data: array [0..0] of double;
  6. end;
  7. PTCabecera =  ^TCabecera;

Tambien definiríamos el Buffer como:


delphi
  1. TBuffer = array [0..1023] of char;
  2. PTBuffer = ^TBuffer;

Supongamos que hacemos una primera lectura del principio del archivo en el buffer. La conversión de ese biffer, de esta forma:


delphi
  1. var
  2. Cabecera: PTCabecera;
  3. begin
  4. Cabecera:= PTCabecera(@Buffer[0]);

Nos da la cabecera buscada y el primer elemento del array Data. Todo lo siguiente son más elementos de Data.

 

El tamaño del buffer debe ser mínimo el de la estructura cabecera y un múlltiplo de sizeof(double). al poner packet nos aseguramos que el compilador respetará el tamaño real de nustra estructura, que es fundamental para hacer bien la conversión del buffer a cabecera.

 

No se si la idea va por el camino que buscas.

 

 

Saludos.

Ummm. El tema del buffer me intuía que tendría que recurrir a punteros, pero no exactamente como. Tendría que ir probando. Si tienes un tiempo libre, ¿podrías hacer un mini ejemplo de como sería el guardado y lectura empleando tu propuesta?

Anoche a última hora tras unas búsquedas sin demasiado resultado llegué a un blog que tiene un artículo que expone el uso de buffer pero de otra forma, y aprovechando la clase TFileStream. En lugar de tener un buffer como el array directamente se vale de variables del tipo adecuado. De modo que al hacer un ReadBuffer() hace un ReadBuffer(variabledetipo, Siseof(variabletipo)). El artículo trata sobre como generar "archivos definidos por el usuario". Lo hace parecer algo simple pero que se necesita de estudio y una correcta lectura de los datos.

Ni bien me desocupe de unos trabajos que tengo continúo con el tema.

 

Hasta donde estaba consultando la documentación la forma de trabajar con buffer indicaba esto:

 


delphi
  1. var
  2. TotalBytesRead, BytesRead : Int64;
  3. Buffer : array [0..4095] of byte; // or, array [0..4095] of char
  4. FileStream : TFileStream;
  5.  
  6. try
  7. FileStream := TFileStream.Create;
  8. FileStream.Position := 0; // Ensure you are at the start of the file
  9. while TotalBytesRead <= FileStream.Size do // While the amount of data read is less than or equal to the size of the stream do
  10. begin
  11. BytesRead := FileStream.Read(Buffer,sizeof(Buffer)); // Read in 4096 of data
  12. inc(TotalBytesRead, BytesRead); // Increase TotalByteRead by the size of the buffer, i.e. 4096 bytes
  13. // Do something with Buffer data
  14. end;

Tengo el cerebro frito. ¡Manden vacaciones! 01.gif

 

Saludos,


  • 0

#6 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 07 April 2015 - 07:00 AM

Esto sería un ejemplo de como leer con un buffer (que podías haber definido como PCHAR) y como reinterpretar su contenido mediante un simple cast:


delphi
  1. type
  2.  
  3. TBuffer = array [0..1023] of char;
  4. PTBuffer = ^TBuffer;
  5.  
  6. TCabecera = packed record
  7. Rows: DWORD;
  8. Cols: DWORD;
  9. Orientation: DWORD;
  10. Data: array [0..0] of double;
  11. end;
  12. PTCabecera = ^TCabecera;
  13.  
  14. ........
  15.  
  16. var
  17. hFile: Integer;
  18. Cabecera: PTCabecera;
  19. Buffer: TBuffer;
  20. L: integer;
  21. begin
  22. hFile:= FileOpen('prueba.bin', fmOpenRead);
  23. L:= FileSeek(hFile,0,2);
  24. FileSeek(hFile,0,0);
  25.  
  26. L:= FileRead(hFile, Buffer[0], L);
  27. Cabecera:= PTCabecera(@Buffer[0]);
  28. FileClose(hFile);
  29. end;

Puedes usarlo con cualquier tipo de buffer. El ejemplo usa un array of char es por eso que uso Buffer[0] para leer y @Buffer[0] para encontrar el puntero. Puedes usar un buffer tipo PChar(AllocMem(Size)); en cuyo caso la sintaxis sería un poco más simple.

 

Cualquier componente (TFileStream) que te permita obtener un buffer de lectura de un fichero, te permite esta conversión.
 

 

Saludos.


  • 1

#7 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 07 April 2015 - 09:19 AM

Bueno, a ver si ya le voy entendiendo. Veo tu ejemplo y esto es lo que voy entendiendo... Primero te vas al final del archivo para tener el tamaño total y lo guardas en L. Te vuelves al inicio y

lees la ¿TOTALIDAD? del achivo.

 

Cuestión que en Cabera tengo ya leído el dato de las dimensiones. Ahora en Buffer[0] hasta el buffer Buffer[1023] tendré los ¿128 primeros doubles? Para moverme entre estos debo ir haciendo saltos de @Buffer[0] + (SizeOf(Double)*iter?

Luego debo volver a hacer un FileRead sobre el Buffer hasta los bytes restantes y repetir el proceso.

 

Vaya que tengo tarea para la tarde.

 

Saludos,


  • 0

#8 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 07 April 2015 - 09:46 AM

No tienes porqué leer todo el archivo. Yo lo hice porque guardé un archivo pequeño de ejemplo. Basta conque leas la cabecera y luego los dados que necesites. Tampoco es necesario que guardes el primer elemento del array de datos en la cabecera, yo lo hice para mostrar que hay una continuidad en memoria entre cabecera y datos, puesto que así está escrito en el archivo.

Cuestión que en Cabera tengo ya leído el dato de las dimensiones. Ahora en Buffer[0] hasta el buffer Buffer[1023] tendré los ¿128 primeros doubles? Para moverme entre estos debo ir haciendo saltos de @Buffer[0] + (SizeOf(Double)*iter?

Luego debo volver a hacer un FileRead sobre el Buffer hasta los bytes restantes y repetir el proceso.

No es así. En Buffer[0] tienes el comienzo de la cabecera, a no ser que Buffer sea el resultado de más lecturas. En Cabecera.Data[0] es donde tienes el primer elemento.

Si lees desde esa posición tendrás el segundo, el tercero... pero antes tendrás que realizar un casting de Buffer a un puntero de tipo array de doubles.

 

Ten en cuenta que Cabecera es una "representación" del Buffer, si quieres guardarla deberás realizar una copia de la misma, pues en sucesivas lecturas Buffer tendrá contenidos diferentes.

 

En mi ejemplo de una sola lectura de todo el archivo, tienes el array de datos inmediato a cabecera.

 

 

Saludos.


  • 0

#9 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 07 April 2015 - 11:21 AM

No tienes porqué leer todo el archivo. Yo lo hice porque guardé un archivo pequeño de ejemplo. Basta conque leas la cabecera y luego los dados que necesites. Tampoco es necesario que guardes el primer elemento del array de datos en la cabecera, yo lo hice para mostrar que hay una continuidad en memoria entre cabecera y datos, puesto que así está escrito en el archivo.

No es así. En Buffer[0] tienes el comienzo de la cabecera, a no ser que Buffer sea el resultado de más lecturas. En Cabecera.Data[0] es donde tienes el primer elemento.

Si lees desde esa posición tendrás el segundo, el tercero... pero antes tendrás que realizar un casting de Buffer a un puntero de tipo array de doubles.

 

Ten en cuenta que Cabecera es una "representación" del Buffer, si quieres guardarla deberás realizar una copia de la misma, pues en sucesivas lecturas Buffer tendrá contenidos diferentes.

 

En mi ejemplo de una sola lectura de todo el archivo, tienes el array de datos inmediato a cabecera.

 

 

Saludos.

 

Ahora si que necesito un cerebro nuevo :

Mi caso será un tanto diferente... tendré matrices y vectores bastante grandes. Asi que leerse todo de una sentada tampoco es buena idea.

 

Inspirado en los ejemplos que ofrece el artículo que cité en el post #5 (sobre todo el ejemplo de como almacenar arrays dinámicos de strings y el de records) me ha surgido la idea de tener un record con la estructura de la cabecera como la que tu ofreces. La leo. Luego armo un array de tamaño variable que lo usaré de Buffer y lo dimensiono del tamaño de filas o columnas según la orientación. Con este bufer leo los datos como si se tratase de un recorrido fila a fila o columna a columna. Después entre cada lectura del buffer simplemente paso los datos a la matriz real.

 

Es como una mezcla de tu propuesta de cabecera (sin el data) y la técnica del write/read Buffer que comenta el artículo. Tendré que ver que sale...

 

Saludos,


  • 0

#10 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4111 posts
  • LocationMadrid - España

Posted 07 April 2015 - 11:44 AM

Ye te comenté que no es necesario leer todo el archivo. Si precisas de todo el array en memoria, entonces lo que dices tiene sentido, puedes usarlo directamente como buffer de lectura y te ahorras una copia, basta con que pases a la función FileRead el primer elemento del array a leer, la lectura del archivo se encargará de rellenarte el array previamente dimensionado.
La lectura y la carga en una misma operación. :)

Saludos.
  • 0

#11 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 09 April 2015 - 06:11 PM

Les comento como procedí al final a almacenar mis volúmenes de datos. Básicamente es una aplicación de las técnicas que se pueden ver en el artículo que señalé en los anteriores mensajes.

 

En lugar de almacenar de a toda una fila o columna por vez debí proceder elemento a elemento. Esto es así debido a que por la otra forma me ha dado problemas.

A pesar de hacerse elemento a elemento, según la orientación que uno decida, el algoritmo es lo bastante rápido aún en matrices y vectores que tengan más del millón de cifras.

Es increíble la velocidad en que trabaja la técnica Buffer.

 

Básicamente la idea es:

0) Opcional, pero altemente recomendable. Guardar un ID, marca, o hasta incluso una especie de versión del archivo que nos permita identificar el "formato" que esperamos. De esta forma es lo primero que vamos a leer, y si comprobamos que lo leído se corresponde a la marca se asume que es un archivo válido y esperado para nuestro propósito:


delphi
  1. FileStream.WriteBuffer(IDMark, SizeOf(IDMark));

1) Guardar una cabecera que contenga los datos necesarios para hacer las operaciones y comprobaciones de lectura y escritura perfectamente. En mi caso por ejemplo, para la matriz necesito almacenar la cantidad de filas, columnas y el sentido u orientación de lectura/escritura en la matriz:


delphi
  1. FileStream.WriteBuffer(Header, SizeOf(Header));

2) Luego se procede a almacenar los datos. En mi caso el contenido propio de la matriz. Para el orden columna a columna se espera algo como:


delphi
  1. for j := 0 to COLS -1 do
  2. for i := 0 to ROWS - 1 do
  3. FileStream.WriteBuffer(Matrix[i,j], SizeOf(MyTypeData)); //o simplemente SizeOf(Matrix[0,0]) o la cantidad de bytes directamente

3) Opcional. Se añade una marca de fin de archivo. Esto permite determinar que acá se espera ya el fin de archivo.


delphi
  1. FileStream.WriteBuffer(IDMarkEnd, SizeOf(IDMarkEnd));

Para la lectura se procede de forma análoga con el método ReadBuffer().

Sabiendo el tamaño en bytes de nuestros tipos empleados y definiendo nuestra propia estructura de archivos es posible determinar la posición exacta de cada elemento que se necesite. Esto puede aplicarse para detectar si hay alguna anomalía en el archivo. Por ejemplo, teniendo la información de la dimensión de la matriz, y recordando el tamaño que ocupan las marcas y la cabecera puedo comprobar si efectivamente la cantidad de elementos es lo que dice ser:


delphi
  1. Elementos := (TamanoArchivo - TamanoEstructuras) div TamanoTipoDato;
  2. if Elementos = Header.Cols * Header.Rows
  3. then ....

Al ser todo basado en Bytes, los datos se tienen en forma "cruda". Por lo que uno puede en realidad leer y guardar cualquier cosa, por ello es fundamental definir una estructura y respetarla.

 

En mi caso estoy sorprendido por la rapidez de ejecución... a archivos del tamaño cercanos a 10MB los abre y genera en un pestaneo. Estoy encantado con esta técnica. 27.gif

 

Saludos,


  • 0





Also tagged with one or more of these keywords: Buffers, archivos

IP.Board spam blocked by CleanTalk.