获取文件特性

在处理文件的过程中,除了直接读写数据,还经常需要分析它们的特性。主要属性之一是文件大小,可以使用 FileSize 函数获得。还有一些特征可以使用 FileGetInteger 获取。

请注意,FileSize 函数需要打开的文件句柄。 FileGetInteger 具有一些可以通过文件名识别的特性,包括大小,无需首先打开文件。

ulong FileSize(int handle)

该函数根据文件的描述符返回打开文件的大小。如果出错,则结果等于 0,该结果是该函数正常执行的有效大小,因此你应始终使用 _LastError(或 GetLastError)分析潜在错误。

要获取文件大小,也可以将指针移动到文件末尾 FileSeek(handle, 0, SEEK_END) 然后调用 FileTell(handle)。这两个函数已在前一节中介绍过。

long FileGetInteger(int handle, ENUM_FILE_PROPERTY_INTEGER property)

long FileGetInteger(const string filename, ENUM_FILE_PROPERTY_INTEGER property, bool common = false)

该函数有两种工作方式:通过打开的文件描述符,以及通过文件名称(包括已关闭文件的名称)。

该函数返回在 property 参数中指定的文件属性之一。每种方式的有效属性列表不同(见下文)。虽然值类型是 long,但是取决于请求的属性,它不仅可能包含整数数字,而且可能包含 datetimebool:显式执行要求的类型强制转换。

通过文件名请求属性时,还可以使用 common 参数指定要搜索的文件所在的文件夹:当前终端文件夹 MQL5/Filesfalse,默认)或者公用文件夹 Users/<user_name>...MetaQuotes/Terminal/Common/Files (true)。如果 MQL 程序正在测试程序中运行,则工作目录位于测试代理文件夹中 (Tester/<agent>/MQL5/Files),参见 处理文件章节的介绍。

下表列出了 ENUM_FILE_PROPERTY_INTEGER 的所有成员。

特性

说明

FILE_EXISTS *

检查存在性(类似于 FileIsExist)

FILE_CREATE_DATE *

创建日期

FILE_MODIFY_DATE *

上次修改日期

FILE_ACCESS_DATE *

上次访问日期

FILE_SIZE *

文件大小,以字节为单位(类似于 FileSize)

FILE_POSITION

文件中的指针位置(类似于 FileTell)

FILE_END

文件末尾位置(类似于 FileIsEnding)

FILE_LINE_END

字符串末尾位置(类似于 FileIsLineEnding)

FILE_IS_COMMON

在终端共享文件夹中打开的文件 (FILE_COMMON)

FILE_IS_TEXT

以文本方式打开的文件 (FILE_TXT)

FILE_IS_BINARY

以二进制方式打开的文件 (FILE_BIN)

FILE_IS_CSV

以 CSV 方式打开的文件 (FILE_CSV)

FILE_IS_ANSI

以 ANSI 方式打开的文件 (FILE_ANSI)

FILE_IS_READABLE

打开进行读取的文件 (FILE_READ)

FILE_IS_WRITABLE

打开进行写入的文件 (FILE_WRITE)

允许由文件名使用的特性标有星号如果你试图获取其它特性,则该函数的第二个版本将返回错误 4003 (INVALID_PARAMETER)。

在处理打开的文件时,某些特性可能改变:FILE_MODIFY_DATE、FILE_ACCESS_DATE、FILE_SIZE、FILE_POSITION、FILE_END、FILE_LINE_END(仅限文本文件)。

如果出错,调用结果为 -1。

该函数的第二个版本始终允许你检查指定的名称是文件名称还是目录名称。如果在通过名称获取特性时指定了目录,则该函数将设置一个特别内部错误代码 5018 (ERR_MQL_FILE_IS_DIRECTORY),而返回的值将是正确的。

我们将使用 FileProperties.mq5 脚本来测试本节的函数。它将处理具有预定义名称的文件。

const string fileprop = "MQL5Book/fileprop";

OnStart 的开头,我们尝试通过错误描述符请求大小(不是通过 File Open 调用获得)。在 FileSize 之后,要求执行_LastError 变量检查,FileGetInteger 立即返回一个特殊值,一个错误指示器 (-1)。

void OnStart()
{
   int handle = 0;
   ulong size = FileSize(handle);
   if(_LastError)
   {
      Print("FileSize error="E2S(_LastError) + "(" + (string)_LastError + ")");
      // We will get: FileSize 0, error=WRONG_FILEHANDLE(5008)
   }
   
   PRTF(FileGetInteger(handleFILE_SIZE)); // -1 / WRONG_FILEHANDLE(5008)

接下来,我们创建一个新文件或者打开一个现有文件并重置,然后写入测试文本。

   handle = PRTF(FileOpen(filepropFILE_TXT | FILE_WRITE | FILE_ANSI)); // 1
   PRTF(FileWriteString(handle"Test Text\n")); // 11

我们选择性地请求某些特性。

   PRTF(FileGetInteger(filepropFILE_SIZE)); // 0, not written to the disk yet
   PRTF(FileGetInteger(handleFILE_SIZE)); // 11
   PRTF(FileSize(handle)); // 11
   PRTF(FileGetInteger(handleFILE_MODIFY_DATE)); //1629730884, number of seconds since 1970
   PRTF(FileGetInteger(handleFILE_IS_TEXT)); // 1, bool true
   PRTF(FileGetInteger(handleFILE_IS_BINARY)); // 0, bool false

通过描述符获取的有关文件长度的信息会考虑到当前缓存缓冲区,如果信息是通过文件名称获取,则实际长度只有在文件关闭后或者调用 FileFlush 函数(参见 将缓存强制写入磁盘)。

该函数返回自 1970 年 1 月 1 日以来的标准历元的日期和时间(以秒来表示),其对应于 datetime 类型并且可以转换为后者。

对于具有描述符的函数版本,文件打开标志(其模式)请求会成功,特别是,我们会收到响应,告知该文件是文本而不是二进制文件。然而,下一个类似的文件名请求将会失败,因为仅当传递了有效句柄时,才支持该特性。即使名称指向我们已经打开的同一文件也会失败。

   PRTF(FileGetInteger(filepropFILE_IS_TEXT)); // -1 / INVALID_PARAMETER(4003)

我们稍作等待,关闭文件,再次检查修改日期(这次通过名称来检查,因为描述符不再有效)。

   Sleep(1000);
   FileClose(handle);
   PRTF(FileGetInteger(filepropFILE_MODIFY_DATE)); // 1629730885 / ok

这里你可以清楚看到时间已经增加 1。

最后,确保特性对目录(文件夹)可用。

   PRTF((datetime)FileGetInteger("MQL5Book"FILE_CREATE_DATE));
   // We will get: 2021.08.09 22:38:00 / FILE_IS_DIRECTORY(5018)

由于本书的所有示例均位于 "MQL5Book" 文件夹,其必须已经存在。不过你的实际创建时间将不同。在此情况下,FILE_IS_DIRECTORY 错误代码由 PRTF 宏显示。在工作程序中,应直接调用函数而不使用宏,然后在 _LastError 中读取代码。