- Métodos de almacenamiento de la información: texto y binario
- Escritura y lectura de archivos en modo simplificado
- Abrir y cerrar archivos
- Gestión de descriptores de archivo
- Seleccionar una codificación para el modo texto
- Escritura y lectura de arrays
- Escritura y lectura de estructuras (archivos binarios)
- Escritura y lectura de variables (archivos binarios)
- Escritura y lectura de variables (archivos de texto)
- Gestión de la posición en un expediente
- Obtención de las propiedades de un archivo
- Forzar escritura de caché en disco
- Eliminación de un archivo y comprobación de su existencia
- Copia y desplazamiento de archivos
- Búsqueda de archivos y carpetas
- Trabajar con carpetas
- Cuadro de diálogo de selección de archivos o carpetas
Gestión de la posición en un archivo
Como ya sabemos, el sistema asocia un determinado puntero a cada archivo abierto que determina el lugar del archivo (offset desde su inicio) en el que se escribirán o leerán los datos la próxima vez que se llame a cualquier función de E/S. Una vez ejecutada la función, el puntero se desplaza según el tamaño de los datos escritos o leídos.
En algunos casos se desea cambiar la posición del puntero sin operaciones de E/S. En concreto, cuando necesitamos añadir datos al final de un archivo, lo abrimos en modo «mixto» FILE_READ | FILE_WRITE, y entonces debemos terminar de alguna manera al final del archivo (de lo contrario, empezaremos a sobrescribir los datos desde el principio). Podríamos llamar a las funciones de lectura mientras hay algo que leer (desplazando así el puntero), pero esto no es eficiente. Es mejor utilizar la función especial FileSeek. Y la función FileTell permite obtener el valor real del puntero (posición en el archivo).
En esta sección exploraremos éstas y un par de funciones más relacionadas con la posición actual en un archivo. Algunas de ellas funcionan de la misma manera para archivos en modo texto y binario, mientras que otras son diferentes.
bool FileSeek(int handle, long offset, ENUM_FILE_POSITION origin)
La función desplaza el puntero del archivo el número offset de bytes utilizando como referencia origin, que es una de las posiciones predefinidas descritas en la enumeración ENUM_FILE_POSITION. offset puede ser positivo (desplazamiento hasta el final del archivo y más allá) o negativo (desplazamiento hasta el principio). ENUM_FILE_POSITION tiene los siguientes miembros:
- SEEK_SET para el inicio del archivo
- SEEK_CUR para la posición actual
- SEEK_END para el final del archivo
Si el cálculo de la nueva posición relativa al punto de anclaje ha dado un valor negativo (es decir, se solicita un desplazamiento a la izquierda del principio del archivo), entonces el puntero del archivo se fijará al principio del archivo.
Si establece la posición más allá del final del archivo (el valor es mayor que el tamaño del archivo), la escritura posterior en el archivo no se realizará desde el final del archivo, sino desde la posición establecida. En este caso, se escribirán valores indefinidos entre el final anterior del archivo y la posición dada (véase más abajo).
La función devuelve true en caso de éxito y false en caso de error.
ulong FileTell(int handle)
Para un archivo abierto con el descriptor handle, la función devuelve la posición actual del puntero interno (un desplazamiento relativo al principio del archivo). En caso de error, se devolverá ULONG_MAX ((ulong)-1). El código de error está disponible en la variable _LastError, o a través de la función GetLastError.
bool FileIsEnding(int handle)
La función devuelve una indicación de si el puntero se encuentra al final del archivo handle. Si es así, el resultado es true.
bool FileIsLineEnding(int handle)
Para un archivo de texto con el descriptor handle, la función devuelve un signo de si el puntero del archivo está al final de la línea (inmediatamente después de los caracteres de nueva línea '\n' o '\r\n'). En otras palabras: el valor de retorno true significa que la posición actual está al principio de la siguiente línea (o al final del archivo). Para archivos binarios, el resultado es siempre false.
El script de prueba de las funciones mencionadas se llama FileCursor.mq5. Funciona con tres archivos: dos binarios y uno de texto.
const string fileraw = "MQL5Book/cursor.raw";
|
Para simplificar el registro de la posición actual, además de los signos de fin de archivo (End-Of-File, EOF) y fin de línea (End-Of-Line, EOL), hemos creado una función de ayuda FileState.
string FileState(int handle)
|
El escenario para probar las funciones en un archivo binario incluye los pasos que figuran a continuación.
Cree un archivo nuevo o abra un archivo fileraw existente («MQL5Book/cursor.raw») en modo lectura/escritura. Inmediatamente después de la apertura, y después de cada operación, mostramos el estado actual del archivo llamando a FileState.
void OnStart()
|
Mueva el puntero al final del archivo, lo que nos permitirá añadir datos a este archivo cada vez que se ejecute el script (y no sobrescribirlo desde el principio). La forma más obvia de referirse al final del archivo es offset nulo relativo a origin=SEEK_END.
PRTF(FileSeek(handle, 0, SEEK_END));
|
Si el archivo ya no está vacío (no es nuevo), podemos leer los datos existentes en su posición arbitraria (relativa o absoluta). En concreto, si el parámetro origin de la función FileSeek es igual a SEEK_CUR, significa que con un offset negativo la posición actual se moverá el número correspondiente de bytes hacia atrás (a la izquierda), y con positivo se moverá hacia delante (a la derecha).
En este ejemplo estamos intentando retroceder el tamaño de un valor de tipo int. Un poco más adelante veremos que en este lugar debe haber un campo day_of_year (último campo) de la estructura MqlDateTime, ya que los escribimos en un archivo en instrucciones posteriores, y estos datos están disponibles en el archivo en la siguiente ejecución. El valor leído se registra para compararlo con el que se guardó anteriormente.
if(PRTF(FileSeek(handle, -1 * sizeof(int), SEEK_CUR)))
|
En un nuevo archivo vacío, la llamada a FileSeek terminará con el error 4003 (INVALID_PARAMETER), y el bloque de sentencia if no se ejecutará.
A continuación, se rellena el archivo con datos. En primer lugar, se escribe la hora local actual del ordenador (8 bytes de datetime) con FileWriteLong.
datetime now = TimeLocal();
|
A continuación, intentamos retroceder desde la ubicación actual 4 bytes (-4) y leemos long.
PRTF(FileSeek(handle, -4, SEEK_CUR));
|
Este intento terminará con el error 5015 (FILE_READERROR), porque estábamos al final del archivo y después de desplazar 4 bytes a la izquierda no podemos leer 8 bytes de la derecha (tamaño long). No obstante, como veremos en el registro, como resultado de este intento fallido el puntero se moverá de nuevo al final del archivo.
Si retrocede 8 bytes (-8), la lectura posterior del valor long se realizará correctamente, y ambos valores de tiempo, incluido el original y el recibido del archivo, deben coincidir.
PRTF(FileSeek(handle, -8, SEEK_CUR));
|
Por último, escriba en el archivo la estructura MqlDateTime rellenada con la misma hora. La posición en el archivo aumentará en 32 (el tamaño de la estructura en bytes).
MqlDateTime mdt;
|
Después de la primera ejecución del script para el escenario con el archivo fileraw (MQL5Book/cursor.raw) obtenemos algo como lo siguiente (el tiempo será diferente):
first run
|
Según el estado, el tamaño del archivo es inicialmente cero porque la posición es «P:0» tras el desplazamiento al final del archivo («F:true»). Después de cada grabación (utilizando FileWriteLong y FileWriteStruct), la posición P se incrementa en el tamaño de los datos escritos.
Tras la segunda ejecución del script podrá observar algunos cambios en el registro:
second run
|
En primer lugar, el tamaño del archivo tras la apertura es 40 (según la posición «P:40» tras el desplazamiento al final del archivo). Cada vez que se ejecute el script, el archivo crecerá en 40 bytes.
En segundo lugar, como el archivo no está vacío, es posible navegar por él y leer los datos «antiguos». En particular, después de retroceder a -1*sizeof(int) desde la posición actual (que es también el final del archivo), leemos con éxito el valor 234, que es el último campo de la estructura MqlDateTime (es el número del día de un año y lo más probable es que sea diferente para usted).
El segundo escenario de prueba funciona con el archivo de texto csv filetxt (MQL5Book/cursor.csv). También lo abriremos en el modo combinado de lectura y escritura, pero no moveremos el puntero al final del archivo. Por eso, cada ejecución del script sobrescribirá los datos, comenzando desde el principio del archivo. Para facilitar la detección de las diferencias, los números de la primera columna del CSV se generan aleatoriamente. En la segunda columna siempre se sustituyen las mismas cadenas de la plantilla en la función StringFormat.
Print(" * Phase II. Text file");
|
He aquí un ejemplo de los datos generados:
34,abc
|
A continuación, volvemos al principio del archivo y lo leemos en un bucle con FileReadString, registrando constantemente el estado.
PRTF(FileSeek(handle, 0, SEEK_SET));
|
A continuación se muestran los registros del archivo filetxt después de la primera y segunda ejecución del script. En primer lugar la primera:
first run
|
Y aquí está la segunda:
second run
|
Como puede ver, el archivo no cambia de tamaño, pero se escriben números diferentes en los mismos desplazamientos. Como este archivo CSV tiene dos columnas, después de cada segundo valor que leemos vemos una bandera EOL («L:true») inclinada.
El número de líneas detectadas es 3, a pesar de que sólo hay 2 caracteres de nueva línea en el archivo: la última (tercera) línea termina con el archivo.
Para terminar, el último escenario de prueba utiliza el archivo file100 (MQL5Book/k100.raw) para mover el puntero más allá del final del archivo (hasta la marca de 1000000 bytes), y así aumentar su tamaño (reserva espacio en disco para posibles futuras operaciones de escritura).
Print(" * Phase III. Allocate large file");
|
La salida de registro para este script no cambia de una ejecución a otra; sin embargo, los datos aleatorios que terminan en el espacio asignado para el archivo pueden diferir (su contenido no se muestra aquí: utilice un visor binario externo).
* Phase III. Allocate large file
|