File Handling In Pascal/zh CN: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: File Handling In Pascal/zh CN
| Old | New | Line | |
|---|---|---|---|
| 1 | + | 大多数程序员都需要知道如何操作文件。文件可以用来保存用户设置、错误日志等等。在这里我将教你如何操作文本文件。 | |
| 2 | + | ||
| 3 | + | # 旧程序风格 | |
| 4 | + | ||
| 5 | + | 使用标准Pascal文件(非面向对象),你可以使用一个文本文件类型,它允许你将字符串写入到文件或创建自己的文件类型。 | |
| 6 | + | ||
| 7 | + | ``` pascal | |
| 8 | + | ... | |
| 9 | + | type | |
| 10 | + | TIntegerFile = file of Integer; // 允许你将整数写入文件 | |
| 11 | + | TPCharFile = file of PChar; // PChars写入到文件 | |
| 12 | + | TStringFile = file of String; // 字符串写入到文件 | |
| 13 | + | ... | |
| 14 | + | ``` | |
| 15 | + | ||
| 16 | + | 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. | |
| 17 | + | ||
| 18 | + | (~~如果我们只做了TStringFile = File,那么它不能被写入任何东西!此外,你不能将整数写入到TStringFile。因为它是一个字符串文件。更好的使用文件类型来存储不同类型的值。~~) | |
| 19 | + | ||
| 20 | + | ## IO | |
| 21 | + | ||
| 22 | + | IO is the file handling thingy for Pascal.(~~IO是Pascal文件处理的~~),它告诉编译器如何处理IO错误:抛出一个异常或将结果存储到IOResult变量。 因为它是一个编译器指令,所以: | |
| 23 | + | ||
| 24 | + | ``` pascal | |
| 25 | + | {$I-} // 关闭检查。将所有的错误存入IOResult变量 | |
| 26 | + | {$I+} // 把它重新打开,将导致EInOutError异常 | |
| 27 | + | ``` | |
| 28 | + | ||
| 29 | + | 通过禁用/关闭 \$I, 文件操作结果进入IOResult变量。这是一个[[Cardinal|基数数字类型]]. 所以,如果你想写IOResult,你必须使用IntToStr功能。不同的数字代表不同的错误。所以你可能要检查文档中不同的错误: [1](http://www.freepascal.org/docs-html/rtl/system/ioresult.html). | |
| 30 | + | ||
| 31 | + | ## 文件程序 | |
| 32 | + | ||
| 33 | + | 这些文件处理过程和函数位于系统单元。请参阅FPC文档了解更多信息: [系统单元参考](http://www.freepascal.org/docs-html/rtl/system/index-5.html). | |
| 34 | + | ||
| 35 | + | - **AssignFile**(或者旧的**Assign**) - 将文件名赋给变量名 | |
| 36 | + | - **Append** - 以附加的方式打开已有的文件 | |
| 37 | + | - **BlockRead** - 读一个或多个记录到变量中 | |
| 38 | + | - **BlockWrite** - 从变量中写一个或多个记录 | |
| 39 | + | - **CloseFile**(或者旧的**Close**) - 关闭打开的文件 | |
| 40 | + | - **EOF** - 测试文件是否到文件尾 | |
| 41 | + | - **Erase** - 删除文件 | |
| 42 | + | - **FilePos** - 返回文件的当前指针位置 | |
| 43 | + | - **FileSize** - 返回当前文件的大小 | |
| 44 | + | - **Flush** - 将缓冲区的内容刷新到输出的文本文件中 | |
| 45 | + | - **IOResult** - 返回最新的I/O操作完成状态 | |
| 46 | + | - **Read** - Read from a text file into variable(从文本文件到变量) | |
| 47 | + | - **ReadLn** - Read from a text file into variable and goto next line(读取文本文件并转到下一行) | |
| 48 | + | - **Reset** - Opens a file for reading(打开文件并读取) | |
| 49 | + | - **Rewrite** - Open file for writing(打开并写入文件) | |
| 50 | + | - **Seek** - Change position in file(更改文件指针位置) | |
| 51 | + | - **SeekEOF** - Set file position to end of file(设置文件位置为结尾) | |
| 52 | + | - **SeekEOLn** - Set file position to end of line(文件位置设置为行结束) | |
| 53 | + | - **Truncate** - Truncate the file at position(截断文件位置) | |
| 54 | + | - **Write** - Write variable to a text file(写入变量到文本文件) | |
| 55 | + | - **WriteLn** - Write variable to a text file and append newline(写入新行到文件) | |
| 56 | + | ||
| 57 | + | ## 示例 | |
| 58 | + | ||
| 59 | + | 一个完整的处理文本文件的示例: | |
| 60 | + | ||
| 61 | + | ``` pascal | |
| 62 | + | program FileTest; | |
| 63 | + | ||
| 64 | + | {$mode objfpc} // 不要忘了这个 | |
| 65 | + | ||
| 66 | + | uses | |
| 67 | + | Sysutils; | |
| 68 | + | ||
| 69 | + | var | |
| 70 | + | FileVar: TextFile; | |
| 71 | + | ||
| 72 | + | begin | |
| 73 | + | WriteLn('File Test'); | |
| 74 | + | AssignFile(FileVar, 'Test.txt'); // 你不需要输出 .txt 但现在需要 | |
| 75 | + | {$I+} // 使用异常处理 | |
| 76 | + | try | |
| 77 | + | Rewrite(FileVar); // 创建文件 | |
| 78 | + | Writeln(FileVar,'Hello'); | |
| 79 | + | // Use CloseFile rather than Close as Close is used in other units as well | |
| 80 | + | CloseFile(FileVar); | |
| 81 | + | except | |
| 82 | + | on E: EInOutError do | |
| 83 | + | begin | |
| 84 | + | Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message); | |
| 85 | + | end; | |
| 86 | + | end; | |
| 87 | + | WriteLn('Program finished. Press enter to stop.'); | |
| 88 | + | ReadLn; | |
| 89 | + | end. | |
| 90 | + | ``` | |
| 91 | + | ||
| 92 | + | 任意编辑器打开这个文件,你将看到'''Hello'! 你可以设置Test.txt文件为只读,再运行程序。这时程序会抛出异常,显示错误消息。 | |
| 93 | + | ||
| 94 | + | > **Note:** 我们使用异常处理({\$I+}),它是对多个文件操作及处理错误时的一个简单方法。你也可以使用{\$I-},但你必须在每次操作后检查但你必须检查IOResult,并修改你的下一步操作。 | |
| 95 | + | ||
| 96 | + | 以下是如何追加/添加到文件的示例: | |
| 97 | + | ||
| 98 | + | ``` pascal | |
| 99 | + | program EditFile; | |
| 100 | + | ||
| 101 | + | {$mode objfpc} | |
| 102 | + | ||
| 103 | + | uses | |
| 104 | + | Sysutils; | |
| 105 | + | ||
| 106 | + | var | |
| 107 | + | File1: TextFile; | |
| 108 | + | ||
| 109 | + | begin | |
| 110 | + | WriteLn('Append file'); | |
| 111 | + | {$I+} | |
| 112 | + | try | |
| 113 | + | AssignFile(File1, 'File.txt'); | |
| 114 | + | { | |
| 115 | + | 我这里测试时,原版示例不能执行报错 Error: Wrong number of parameters specified for call to "Append" | |
| 116 | + | 在Lazarus 1.2.2,FPC2.6.4上 | |
| 117 | + | } | |
| 118 | + | // Append(File1, 'adding some text...'); | |
| 119 | + | { 修改后的程序 } | |
| 120 | + | Append(File1); | |
| 121 | + | Writeln(FileVar,'adding some text...'); | |
| 122 | + | Writeln | |
| 123 | + | CloseFile(File1); | |
| 124 | + | except | |
| 125 | + | on E: EInOutError do | |
| 126 | + | begin | |
| 127 | + | Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message); | |
| 128 | + | end; | |
| 129 | + | end; | |
| 130 | + | WriteLn('Program finished. Press enter to stop.'); | |
| 131 | + | Readln; | |
| 132 | + | end. | |
| 133 | + | ``` | |
| 134 | + | ||
| 135 | + | 读取文件: | |
| 136 | + | ||
| 137 | + | ``` pascal | |
| 138 | + | program ReadFile; | |
| 139 | + | ||
| 140 | + | {$mode objfpc} | |
| 141 | + | ||
| 142 | + | uses | |
| 143 | + | Sysutils; | |
| 144 | + | ||
| 145 | + | var | |
| 146 | + | File1: TextFile; | |
| 147 | + | Str: String; | |
| 148 | + | ||
| 149 | + | begin | |
| 150 | + | Writeln('File Reading:'); | |
| 151 | + | AssignFile(File1, 'File.txt'); | |
| 152 | + | {$I+} | |
| 153 | + | try | |
| 154 | + | Reset(File1); | |
| 155 | + | repeat | |
| 156 | + | Readln(File1, Str); // 从文件中读取一行 | |
| 157 | + | Writeln(Str); // 显示这行 | |
| 158 | + | until(EOF(File1)); // EOF(文件结束)程序将继续读取直到文件结尾。 | |
| 159 | + | CloseFile(File1); | |
| 160 | + | except | |
| 161 | + | on E: EInOutError do | |
| 162 | + | begin | |
| 163 | + | Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message); | |
| 164 | + | end; | |
| 165 | + | end; | |
| 166 | + | WriteLn('Program finished. Press enter to stop.'); | |
| 167 | + | Readln; | |
| 168 | + | end. | |
| 169 | + | ``` | |
| 170 | + | ||
| 171 | + | 可以做一些文件处理使用字符而不是字符串。这使得它看起来酷:D。 | |
| 172 | + | ||
| 173 | + | # 对象风格 | |
| 174 | + | ||
| 175 | + | 除了上面提到的旧式文件处理例程,在新系统更高的抽象层次里使用流(数据流)的概念,这意味着作为一个程序员在处理文件时执行更少的步骤。 | |
| 176 | + | ||
| 177 | + | 此外,大部分字符串处理类可以加载(保存)内容从(到)文件。这些方法通常是SaveToFile和LoadFromFile。很多其他对象(像Lazarus网格)也有类似的功能,包括Lazarus数据集(DBExport);it pays to look through the documentation/source code before trying to roll your own import/export routines. | |
| 178 | + | ||
| 179 | + | ## 二进制文件 | |
| 180 | + | ||
| 181 | + | 为操作文件应该使用直接使用TFileStream。这个类封装了系统程序,FileCreate,FileRead,FileWrite,FileSeek和FileClose它在FileUtil单元。 | |
| 182 | + | ||
| 183 | + | [IO 例程](http://www.freepascal.org/docs-html/rtl/sysutils/ioroutines.html) | |
| 184 | + | ||
| 185 | + | 在下面的例子中,注意我们是如何封装处理文件在try... finally块中。这可以确保文件流对象总是释放(在finally... 部分),即使有文件访问(或其他)错误。 | |
| 186 | + | ||
| 187 | + | ``` pascal | |
| 188 | + | var | |
| 189 | + | Buffer: array[0..9999] of Byte; | |
| 190 | + | begin | |
| 191 | + | with TFileStream.Create('SomeFile.bin', fmCreate) do | |
| 192 | + | try | |
| 193 | + | Seek('Hello'); | |
| 194 | + | Write(Buffer, SizeOf(Buffer)); | |
| 195 | + | finally | |
| 196 | + | Free; | |
| 197 | + | end; | |
| 198 | + | end; | |
| 199 | + | ``` | |
| 200 | + | ||
| 201 | + | 你可以加载整个文件到内存中,如果文件大小大于系统可用内存,你的操作系统会开始使用页面/交换文件,making the exercise useless 从性能角度来看。 | |
| 202 | + | ||
| 203 | + | ``` pascal | |
| 204 | + | begin | |
| 205 | + | with TMemoryStream.Create do | |
| 206 | + | try | |
| 207 | + | LoadFromFile('SomeFile.bin'); | |
| 208 | + | Seek(0, soEnd); | |
| 209 | + | Write(Ord('A'), 1); | |
| 210 | + | SaveToFile('SomeFile.bin'); | |
| 211 | + | finally | |
| 212 | + | Free; | |
| 213 | + | end; | |
| 214 | + | end; | |
| 215 | + | ``` | |
| 216 | + | ||
| 217 | + | 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. | |
| 218 | + | ||
| 219 | + | (~~你可能需要读取许多Gb大文件,比方说4096 字节(建议你使用集群文件系统或块大小的整数倍)并从缓冲区里读取数据。~~) | |
| 220 | + | ||
| 221 | + | ``` pascal | |
| 222 | + | var | |
| 223 | + | TotalBytesRead, BytesRead : Int64; | |
| 224 | + | Buffer : array [0..4095] of byte; // 或 array [0..4095] 为 char | |
| 225 | + | FileStream : TFileStream; | |
| 226 | + | ||
| 227 | + | try | |
| 228 | + | FileStream := TFileStream.Create; | |
| 229 | + | FileStream.Position := 0; // 确保在文件开始位置 | |
| 230 | + | while TotalBytesRead <= FileStream.Size do // 当总读取大小 <= 文件大小 | |
| 231 | + | begin | |
| 232 | + | BytesRead := FileStream.Read(Buffer,sizeof(Buffer)); // 读取 4096 字节数据 | |
| 233 | + | inc(TotalBytesRead, BytesRead); // 增加 TotalByteRead 缓冲区的大小,也就是说 4096 字节 | |
| 234 | + | // 做一些与缓冲数据相关的操作 | |
| 235 | + | end; | |
| 236 | + | ``` | |
| 237 | + | ||
| 238 | + | ### 复制文件 | |
| 239 | + | ||
| 240 | + | 与上述,我们可以实现一个简单的FileCopy功能(FreePascal has none in its RTL although Lazarus has one) - adjust as needed for bigger files etc:~~(FreePascal现在没有Lzarus中的RTL中有一个)大文件等需要做些调整:~~ | |
| 241 | + | ||
| 242 | + | ``` pascal | |
| 243 | + | function FileCopy(Source, Target: string): boolean; | |
| 244 | + | // 复制文件 source 到 target;覆盖目标文件 | |
| 245 | + | // 在内存中缓存整个文件 | |
| 246 | + | // 成功返回 true,失败返回false | |
| 247 | + | var | |
| 248 | + | MemBuffer: TMemoryStream; | |
| 249 | + | begin | |
| 250 | + | result:=false; | |
| 251 | + | MemBuffer:=TMemoryStream.Create; | |
| 252 | + | try | |
| 253 | + | try | |
| 254 | + | MemBuffer.LoadFromFile(Source); | |
| 255 | + | MemBuffer.Position:=0; | |
| 256 | + | MemBuffer.SaveToFile(Target); //可能是源文件相同 | |
| 257 | + | result:=true; | |
| 258 | + | except | |
| 259 | + | result:=false; //忽略异常,转换为错误代码 | |
| 260 | + | end; | |
| 261 | + | finally | |
| 262 | + | MemBuffer.Free; | |
| 263 | + | end; | |
| 264 | + | end; | |
| 265 | + | ``` | |
| 266 | + | ||
| 267 | + | ## 文本文件 | |
| 268 | + | ||
| 269 | + | 一般情况下,对于文本文件可以使用 TStringList 类将整个文件加载到内存中,并可以对行进行简单存取。当然,你也可以保存StringList到文件: | |
| 270 | + | ||
| 271 | + | ``` pascal | |
| 272 | + | begin | |
| 273 | + | with TStringList.Create do | |
| 274 | + | try | |
| 275 | + | Add('Hello'); | |
| 276 | + | SaveToFile('SomeFile.txt'); | |
| 277 | + | finally | |
| 278 | + | Free; | |
| 279 | + | end; | |
| 280 | + | end; | |
| 281 | + | ``` | |
| 282 | + | ||
| 283 | + | 为了写单个字符串到流可能需要使用以下过程: | |
| 284 | + | ||
| 285 | + | ``` pascal | |
| 286 | + | procedure SaveStringToPath(theString, filePath: String); | |
| 287 | + | var | |
| 288 | + | textFile: TFileStream = nil; | |
| 289 | + | textLength: integer; | |
| 290 | + | stringBuffer: ^String; | |
| 291 | + | begin | |
| 292 | + | textLength := length(theString); | |
| 293 | + | try | |
| 294 | + | textFile := TFileStream.Create(filePath, fmOpenWrite or fmCreate); | |
| 295 | + | { write string to stream while avoiding to write the initial length } | |
| 296 | + | { 写字符串到流,同时避免写入超过最初长度 } | |
| 297 | + | stringBuffer := @theString + 1; | |
| 298 | + | textFile.WriteBuffer(stringBuffer^, textLength); | |
| 299 | + | finally | |
| 300 | + | if textFile <> nil then textFile.Free; | |
| 301 | + | end; | |
| 302 | + | end; | |
| 303 | + | ``` | |
| 304 | + | ||
| 305 | + | ## 参见 | |
| 306 | + | ||
| 307 | + | - [[File]] | |
| 308 | + | ||
| 309 | + | --- | |
| 310 | + | *From the Free Pascal wiki, page "File Handling In Pascal/zh CN", as it stood on 2020-02-14 after a change by Trev, out of the wiki's dump of 2024-05-26. 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](/wiki-contact).* |
