当面临这种问题时,您可以采用以下两种方法之一:
1. 您提出的方法:对于读取的每个记录,将记录编号(或输入文件返回的位置)写入单独的书签文件。为确保恢复到离开的地方,以免引入重复记录,必须在每次写操作(书签、输出和拒绝文件)后调用 `fflush`。总的来说,这会使典型情况下的性能显著降低。为了完整起见,请注意您有三种方式将内容写入书签文件:
- `fopen(..., 'w') / fwrite / fclose` - 极其缓慢
- `rewind / truncate / fwrite / fflush` - 稍微快一些
- `rewind / fwrite / fflush` - 较快;因为记录编号(或 `ftell` 位置)始终与上一个记录编号(或 `ftell` 位置)一样长或更长,并且会完全覆盖它,所以可以跳过 `truncate`,前提是在启动时截断文件(这回答了您最初的问题)
2. 假设大多数情况下一切顺利,在故障之后恢复时,只需计算已输出的记录数量(正常输出加上拒绝),并从输入文件中跳过相同数量的记录。
- 这会使典型情况下的性能非常快,而在故障后恢复的性能也不会受到显著影响。
- 您无需频繁调用 `fflush`。仅需在切换到拒绝文件写入之前刷新主输出文件,以及在切换回主输出文件写入之前刷新拒绝文件即可(对于 500k 记录的输入,可能需要多次调用 `fflush`)。从输出/拒绝文件中删除最后一行未结束的内容,这样一直到该行之前的所有内容将是一致的。
我强烈建议使用第二种方法。与方法1涉及的写操作相比,任何额外(带缓冲)的读操作所需的开销都非常小(`fflush` 可能需要几毫秒;将其乘以 500k,你就得到了几分钟 - 而在一个含有500k记录的文件中计算行数只需要几秒钟,并且更重要的是,文件系统缓存正在与您一起工作,而不是反过来)。
编辑:想要澄清实现第二种方法所需的确切步骤。
当分别写入输出文件和拒绝文件时,只需要在从一个文件切换到另一个文件时刷新。考虑以下情况作为对执行这些“切换时刷出”的必要性的说明:
假设您向主输出文件写入了1000个记录,然后
您必须先手动清除主输出文件,然后再写1行到拒绝文件中,
接着,您向主输出文件再写入200行,而无需先手动清除拒绝文件,
运行时自动为您刷新了主输出文件,因为您已经在主输出文件的缓冲区中积累了大量数据,即1200条记录。
但是,运行时尚未自动将拒绝文件刷新到磁盘上,因为文件缓冲区仅包含一条记录,这不足以自动刷新。
此时您的程序崩溃了。
您恢复并计算主输出文件中的1200个记录(运行时已经为您刷新了这些记录),但拒绝文件中没有任何记录(未刷新)。
您恢复处理输入文件,从第1201条记录开始,假设您只成功地处理了1200条记录到主输出文件;被拒绝的记录将丢失,第1200个有效记录将重复。
您不希望出现这种情况!
现在考虑在切换输出/拒绝文件后手动刷新:
假设您向主输出文件写入了1000个记录,然后
您遇到一个无效的记录,它属于拒绝文件;最后一条记录是有效的;这意味着您正在切换到写入拒绝文件:在写入拒绝文件之前清除主输出文件,
现在您向拒绝文件写入1行,然后
您遇到一条有效记录,它属于主输出文件;最后一条记录是无效的;这意味着您正在切换到写入主输出文件:在写入主输出文件之前清除拒绝文件,
然后,您向主输出文件再写入200行,而无需先手动清除拒绝文件,
假设运行时没有为您自动刷新任何内容,因为从上次在主输出文件上的手动刷新以来缓冲了200条记录,这不足以触发自动刷新。
此时您的程序崩溃了。
您恢复并计算主输出文件中的1000个有效记录(在切换到拒绝文件之前手动清除了这些记录),以及拒绝文件中的1条记录(在切换回主输出文件之前手动清除)。
您将正确地从第1001条记录开始处理输入文件,即在无效记录之后立即处理第一个有效记录。
您重新处理下一个200个有效记录,因为它们没有被刷新,但您不会丢失任何记录或重复任何记录。
如果您不满意运行时自动刷新之间的时间间隔,则还可以每100或每1000个记录手动刷新。这取决于处理记录是否比刷新更昂贵(如果处理更昂贵,则经常刷新,可能在每个记录之后,否则仅在在输出/拒绝之间切换时刷新。)
从故障中恢复:
打开输出文件和拒绝文件进行“读写”,并开始读取和计数每个记录(例如,在records_resume_counter中),直到达到文件结尾
除非您在输出每个记录后清除,否则您还需要对输出文件和拒绝文件的最后一条记录执行一些特殊处理:
从中断的输出/拒绝文件读取记录之前,请记住您在该输出/拒绝