File Handling In Pascal/zh CN

From Lazarus wiki
Jump to navigationJump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

العربية (ar) English (en) español (es) suomi (fi) français (fr) 日本語 (ja) русский (ru) 中文(中国大陆)‎ (zh_CN) 中文(台灣)‎ (zh_TW)

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

旧程序风格

使用标准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变量。 因为它是一个编译器指令,所以:

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

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

文件程序

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

  • AssignFile(或者旧的Assign) - 将文件名赋给变量名
  • Append - 以附加的方式打开已有的文件
  • BlockRead - 读一个或多个记录到变量中
  • BlockWrite - 从变量中写一个或多个记录
  • CloseFile(或者旧的Close) - 关闭打开的文件
  • EOF - 测试文件是否到文件尾
  • Erase - 删除文件
  • FilePos - 返回文件的当前指针位置
  • FileSize - 返回当前文件的大小
  • Flush - 将缓冲区的内容刷新到输出的文本文件中
  • IOResult - 返回最新的I/O操作完成状态
  • Read - Read from a text file into variable(从文本文件到变量)
  • ReadLn - Read from a text file into variable and goto next line(读取文本文件并转到下一行)
  • Reset - Opens a file for reading(打开文件并读取)
  • Rewrite - Open file for writing(打开并写入文件)
  • Seek - Change position in file(更改文件指针位置)
  • SeekEOF - Set file position to end of file(设置文件位置为结尾)
  • SeekEOLn - Set file position to end of line(文件位置设置为行结束)
  • Truncate - Truncate the file at position(截断文件位置)
  • Write - Write variable to a text file(写入变量到文本文件)
  • WriteLn - Write variable to a text file and append newline(写入新行到文件)


示例

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

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文件为只读,再运行程序。这时程序会抛出异常,显示错误消息。

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


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

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.

读取文件:

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... 部分),即使有文件访问(或其他)错误。

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 从性能角度来看。

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 字节(建议你使用集群文件系统或块大小的整数倍)并从缓冲区里读取数据。)

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中有一个)大文件等需要做些调整:

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到文件:

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

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

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;

参见