维基副本镜像

这是 pascal.chat 维基的镜像。编辑、历史记录和登录都在那里。

File Handling In Pascal/zh CN

English · Español · Français · Suomi · Русский · العربية · 中文(中国) · 中文(台灣) · 日本語

大多数程序员都需要知道如何操作文件。文件可以用来保存用户设置、错误日志等等。在这里我将教你如何操作文本文件。

旧程序风格

使用标准Pascal文件(非面向对象),你可以使用一个文本文件类型,它允许你将字符串写入到文件或创建自己的文件类型。

pascal
...
    type
     TIntegerFile = file of Integer; // 允许你将整数写入文件
     TPCharFile = file of PChar; // PChars写入到文件
     TStringFile = file of String; // 字符串写入到文件
    ...

If we only did TStringFile = File, then it would be impossible to write anything into it! Also, you cannot write integers directly into TStringFile, because it is a file of strings. Better use the filetype TextFile for writing values of different types.

(如果我们只做了TStringFile = File,那么它不能被写入任何东西!此外,你不能将整数写入到TStringFile。因为它是一个字符串文件。更好的使用文件类型来存储不同类型的值。)

IO

IO is the file handling thingy for Pascal.(IO是Pascal文件处理的),它告诉编译器如何处理IO错误:抛出一个异常或将结果存储到IOResult变量。 因为它是一个编译器指令,所以:

pascal
{$I-} // 关闭检查。将所有的错误存入IOResult变量
    {$I+} // 把它重新打开,将导致EInOutError异常

通过禁用/关闭 $I, 文件操作结果进入IOResult变量。这是一个基数数字类型. 所以,如果你想写IOResult,你必须使用IntToStr功能。不同的数字代表不同的错误。所以你可能要检查文档中不同的错误: 1(http://www.freepascal.org/docs-html/rtl/system/ioresult.html).

文件程序

这些文件处理过程和函数位于系统单元。请参阅FPC文档了解更多信息: 系统单元参考.

示例

一个完整的处理文本文件的示例:

pascal
program FileTest;
    
    {$mode objfpc} // 不要忘了这个
    
    uses
     Sysutils;
    
    var
     FileVar: TextFile;
    
    begin
      WriteLn('File Test');
      AssignFile(FileVar, 'Test.txt'); // 你不需要输出 .txt 但现在需要
      {$I+} // 使用异常处理
      try  
        Rewrite(FileVar);  // 创建文件
        Writeln(FileVar,'Hello');
        // Use CloseFile rather than Close as Close is used in other units as well
        CloseFile(FileVar);
      except
        on E: EInOutError do
        begin
         Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
        end;    
      end;
      WriteLn('Program finished. Press enter to stop.');  
      ReadLn;
    end.

任意编辑器打开这个文件,你将看到Hello'! 你可以设置Test.txt文件为只读,再运行程序。这时程序会抛出异常,显示错误消息。

Note-icon.png

Note: 我们使用异常处理({$I+}),它是对多个文件操作及处理错误时的一个简单方法。你也可以使用{$I-},但你必须在每次操作后检查但你必须检查IOResult,并修改你的下一步操作。

以下是如何追加/添加到文件的示例:

pascal
program EditFile;
    
    
    {$mode objfpc}
    
    uses
     Sysutils;
    
    var
     File1: TextFile;
     
    begin
      WriteLn('Append file');
      {$I+}
      try
        AssignFile(File1, 'File.txt');
        { 
        我这里测试时,原版示例不能执行报错 Error: Wrong number of parameters specified for call to "Append"
        在Lazarus 1.2.2,FPC2.6.4上
        }
        // Append(File1, 'adding some text...'); 
        { 修改后的程序 }
        Append(File1);
        Writeln(FileVar,'adding some text...'); 
        Writeln
        CloseFile(File1);
      except
        on E: EInOutError do
        begin
         Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
        end;    
      end;
      WriteLn('Program finished. Press enter to stop.');  
      Readln;
    end.

读取文件:

pascal
program ReadFile;
    
    
    {$mode objfpc}
    
    uses
     Sysutils;
    
    var
     File1: TextFile;
     Str: String;
    
    begin
      Writeln('File Reading:');
      AssignFile(File1, 'File.txt');
      {$I+}
      try
        Reset(File1);
        repeat
          Readln(File1, Str); // 从文件中读取一行
          Writeln(Str); // 显示这行
        until(EOF(File1)); // EOF(文件结束)程序将继续读取直到文件结尾。
        CloseFile(File1);
      except
        on E: EInOutError do
        begin
         Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
        end;    
      end;
      WriteLn('Program finished. Press enter to stop.');  
      Readln;
    end.

可以做一些文件处理使用字符而不是字符串。这使得它看起来酷:D。

对象风格

除了上面提到的旧式文件处理例程,在新系统更高的抽象层次里使用流(数据流)的概念,这意味着作为一个程序员在处理文件时执行更少的步骤。

此外,大部分字符串处理类可以加载(保存)内容从(到)文件。这些方法通常是SaveToFile和LoadFromFile。很多其他对象(像Lazarus网格)也有类似的功能,包括Lazarus数据集(DBExport);it pays to look through the documentation/source code before trying to roll your own import/export routines.

二进制文件

为操作文件应该使用直接使用TFileStream。这个类封装了系统程序,FileCreate,FileRead,FileWrite,FileSeek和FileClose它在FileUtil单元。

IO 例程

在下面的例子中,注意我们是如何封装处理文件在try... finally块中。这可以确保文件流对象总是释放(在finally... 部分),即使有文件访问(或其他)错误。

pascal
var
      Buffer: array[0..9999] of Byte;
    begin
      with TFileStream.Create('SomeFile.bin', fmCreate) do 
      try
        Seek('Hello');
        Write(Buffer, SizeOf(Buffer));
      finally
        Free;
      end;
    end;

你可以加载整个文件到内存中,如果文件大小大于系统可用内存,你的操作系统会开始使用页面/交换文件,making the exercise useless 从性能角度来看。

pascal
begin
      with TMemoryStream.Create do 
      try
        LoadFromFile('SomeFile.bin');
        Seek(0, soEnd);
        Write(Ord('A'), 1);
        SaveToFile('SomeFile.bin');
      finally
        Free;
      end;
    end;

With larger files of many Gb, you may want to read in buffers of, say, 4096 bytes (you're advised to use a multiple of the filesytem cluster or block size) and do something with the data of each buffer read.

(你可能需要读取许多Gb大文件,比方说4096 字节(建议你使用集群文件系统或块大小的整数倍)并从缓冲区里读取数据。)

pascal
var
      TotalBytesRead, BytesRead : Int64;
      Buffer : array [0..4095] of byte;  // 或 array [0..4095] 为 char
      FileStream : TFileStream;
    
    try
      FileStream := TFileStream.Create;
      FileStream.Position := 0;  // 确保在文件开始位置
      while TotalBytesRead <= FileStream.Size do  // 当总读取大小 <= 文件大小
      begin
        BytesRead := FileStream.Read(Buffer,sizeof(Buffer));  // 读取 4096 字节数据
        inc(TotalBytesRead, BytesRead);                       // 增加 TotalByteRead 缓冲区的大小,也就是说 4096 字节
        // 做一些与缓冲数据相关的操作
      end;

复制文件

与上述,我们可以实现一个简单的FileCopy功能(FreePascal has none in its RTL although Lazarus has one) - adjust as needed for bigger files etc:(FreePascal现在没有Lzarus中的RTL中有一个)大文件等需要做些调整:

pascal
function FileCopy(Source, Target: string): boolean;
    // 复制文件 source 到 target;覆盖目标文件
    // 在内存中缓存整个文件
    // 成功返回 true,失败返回false
    var
      MemBuffer: TMemoryStream;
    begin
      result:=false;
      MemBuffer:=TMemoryStream.Create;
      try
        try
          MemBuffer.LoadFromFile(Source);
          MemBuffer.Position:=0;
          MemBuffer.SaveToFile(Target); //可能是源文件相同
          result:=true;
        except
          result:=false; //忽略异常,转换为错误代码
        end;
      finally
        MemBuffer.Free;
      end;
    end;

文本文件

一般情况下,对于文本文件可以使用 TStringList 类将整个文件加载到内存中,并可以对行进行简单存取。当然,你也可以保存StringList到文件:

pascal
begin
      with TStringList.Create do 
      try
        Add('Hello');
        SaveToFile('SomeFile.txt');
      finally
        Free;
      end;
    end;

为了写单个字符串到流可能需要使用以下过程:

pascal
procedure SaveStringToPath(theString, filePath: String);
    var
      textFile: TFileStream = nil;
      textLength: integer;
      stringBuffer: ^String;
    begin
      textLength := length(theString);
      try
        textFile := TFileStream.Create(filePath, fmOpenWrite or fmCreate);
        { write string to stream while avoiding to write the initial length }
        { 写字符串到流,同时避免写入超过最初长度 }
        stringBuffer := @theString + 1;
        textFile.WriteBuffer(stringBuffer^, textLength);
      finally
        if textFile <> nil then textFile.Free;
      end;
    end;

参见


From the Free Pascal wiki, page "File Handling In Pascal/zh CN", as the Internet Archive kept it on 2022-08-19, last changed on 2020-02-15. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

版本4,最后由WikiArchive于修改。