Jerry说得对,如果你只是担心Ctrl-C,那么你可以暂时忽略SIGINT信号。如果你想对进程死亡进行全面的保护,你需要一些形式的最小日志记录。为了交换两个元素:
1)在文件结尾或单独的文件中向控制结构添加一个记录,指示您要交换的文件中的哪两个元素A和B。
2)将A复制到临时空间,记录已经完成此操作,然后执行刷新操作。
3)将B复制到A位置,然后在临时空间记录您已完成此操作,再次刷新。
4)从临时空间复制到B位置。
5)删除记录。
这对于所有实际目的而言都是O(1)额外空间的,在大多数定义下仍然被视为就地排序。理论上,如果n可以任意大,则记录索引的时间复杂度为O(log n):实际上,对数值非常小,合理的硬件/运行时间将其上限限制在64个左右。
无论何时我说“刷新”,我指的是提交足够的更改。有时,基本的刷新操作只会在进程内部刷新缓冲区,但它并不会将物理介质同步,因为它不能完全刷新所有OS/设备驱动程序/硬件级别的缓冲区。当你只担心进程死亡时,这足够了,但如果你担心介质突然卸载,那么你必须刷新到驱动程序之后。如果你担心断电,你必须同步硬件,但你没有。如果你有UPS或者认为停电很少发生,你可以不用担心丢失数据。
在启动时,检查临时空间中是否存在任何“正在交换中”的记录。如果您找到一个,就计算出已经完成的比例,从那里开始完成交换以使数据回到可用状态。然后重新开始排序。
显然这里存在性能问题,因为你要写两倍于之前的记录,并且刷新/同步可能非常昂贵。在实践中,你的原地排序可能包含一些复合移动操作,涉及许多交换,但是你可以进行优化,避免每个元素都到达缓存空间。你只需要确保在覆盖任何数据之前,你有一个安全的副本和一个记录该副本应该放置在哪里以便将文件恢复到只包含每个元素的状态。
杰瑞也正确指出,对于大多数实际目的来说,真正的原地排序太困难和慢了。如果你可以抽出原始文件大小的某些线性部分作为缓存空间,那么用归并排序会更好。
根据你的澄清,即使使用原位排序,你也不需要执行任何刷新操作。你需要在内存中具有相同功能的缓存空间,你的SIGINT处理程序可以访问它,以便在退出之前将数据安全保存,而不是在异常退出后在启动时恢复,并且你需要以信号安全的方式访问该内存(技术上意味着使用sig_atomic_t标志已进行的更改)。即使如此,与其使用真正的原位排序,你可能还是更适合使用归并排序。