如何确保文件已成功写入?

时间:2010-11-07 05:30:35

标签: java file serialization file-io operating-system

我正在为Java中的图形应用程序添加自动保存功能。应用程序定期自动保存当前文档,并在退出时自动保存。当用户启动应用程序时,将重新加载自动保存文件。

如果自动保存文件以任何方式损坏(我假设当文件处于保存状态时会停电会这样做吗?),用户将失去工作。如何防止这种情况并尽我所能保证自动保存文档处于一致状态?

更复杂的是,要自动保存文档,我需要保存一个.xml文件和几个.png文件。此外,.png保存发生在JNI上的C代码中。

我当前的策略是使用扩展名.png.tmp编写每个.png,编写扩展名为.xml.tmp的.xml文件,然后重命名每个文件以删除.tmp部分,直到最后一个.xml 。在启动时,我只加载自动保存文件,如果我能找到.xml文件并忽略.xml.tmp文件。在重命名新文档的.xml.tmp文件之前,我也不会删除以前的自动保存文档。

我想我对你写入磁盘时会发生什么的了解很少。我知道在使用文件时,您可以拥有软件读/写缓冲区,以及操作系统和硬件缓冲区,并且所有这些都需要刷新。我很困惑如何确切地知道什么东西真的被写入磁盘以及我可以做些什么来保护自己。重命名操作是否可以确保刷新缓冲区?

2 个答案:

答案 0 :(得分:7)

  

如果自动保存文件以任何方式损坏(我假设当文件处于保存状态时会停电会这样做吗?),用户将失去工作。如何防止这种情况并尽我所能保证自动保存文档处于一致状态?

为防止因部分写入的自动保存文件而导致数据丢失,请勿覆盖自动保存文件。相反,每次都写入一个新文件,然后在文件安全写入后重命名。

为防止未注意到自动保存文件未正确写入:

  1. 注意在写入和关闭自动保存文件时抛出的异常,以防光盘错误,文件系统已满等等。
  2. 在文件写入时保持文件的运行校验和,并将其写在文件末尾。然后,当您加载自动保存文件时,检查校验和是否存在且是否正确。
  3. 如果检查点状态涉及多个文件,请确保以众所周知的顺序编写文件(不覆盖!),并在安全关闭所有其他文件后在自动保存文件上写入校验和。您可能希望为每个检查点创建一个目录。

    关注

    没有。我不是说重命名总是成功的。但是,它原子 - 它要么成功(并完成),要么文件系统不会更改。所以,如果你这样做:

    1. 写“file.new”并关闭,
    2. 删除“文件”,
    3. 将“file.new”重命名为“file”
    4. 然后提供第一步成功,保证在光盘上安全地拥有最新的“文件”。添加几个步骤很简单,这样您就可以随时备份“文件”。 (如果第3步失败,则会留下“file.new”而没有“文件”。这可以手动恢复,也可以在下次运行时由应用程序自动恢复。)

      另外,我并不是说写入总是成功,或者应用程序不会崩溃,或者说电源永远不会消失。校验和的重点是允许您检测发生这些事情并且自动保存文件不完整的情况。

      最后,如果您的应用程序进入一个状态,其数据结构混乱并且最后一次自动保存因此是无意义的,那么最好有两个自动保存。 (校验和不会防止这种情况。)当应用程序因同样的原因崩溃时要小心自动保存。

答案 1 :(得分:0)

顺便说一句,由于你有几个不同的文件作为这一个文档的一部分,考虑使用项目目录将它们全部保存在一起,或者使用一些封装格式(如.zip)将它们全部放在一个文件中。

您要做的是原子地用新的替换旧备份文件。不幸的是,我不相信Java会给你足够的控制权。您还需要推断底层操作系统中哪些操作是原子操作。我知道Linux文件系统,所以我的答案将偏向于在该系统上运行的Java程序。如果Windows没有做同样的事情我会感到震惊,但我不能肯定地说。

大多数Linux文件系统(例如元数据日志系统)允许您以原子方式重命名文件。如果系统在重命名中途崩溃,那么当您重新启动时,就好像您从未重新命名过一个文件。因此,原子更新现有文件F的常用方法是将新数据写入临时文件T,然后将T重命名为F.任何系统或应用程序崩溃到该重命名都不会影响F,因此它将始终保持一致。

当然,在重命名之前,您需要确保临时文件是一致的。确保将文件的所有流缓冲区刷新到操作系统(Channel.force()OutputStream.flush()),并将操作系统缓冲区刷新到磁盘(FileOutputStream.getFD.sync())。当然,除非您的操作系统禁用硬盘本身上的写入缓存 (它可能没有),否则您的数据仍有可能被破坏。如果您真的想要确定,请在XML中添加校验和。如果你真的是偏执狂,你应该刷新操作系统和硬盘缓冲区缓存并重新读取文件以验证它是否一致。对于正常的消费者应用来说,这超出了任何合理的期望。

但这只是原子地写一个文件。您的问题更复杂:您有许多文件可以自动更新。例如,我会说你有两个文件, img.png main.xml 。我会做其中一个:

  1. 简单的解决方案是创建一个per-savefile目录。您不必担心重命名每个单独的文件,并且您仍然可以原子地将新备份目录重命名为您要替换的旧备份目录。也就是说,如果您的旧备份是 bak / img.png bak / main.xml ,请写下 bak.tmp / img.png bak.tmp / main.xml 并将 bak.tmp 重命名为 bak
  2. 将新的辅助文件命名为其他内容,并让它们与旧的辅助文件共存一段时间。也就是说,写 img.2.png main.xml.tmp (应该引用 img.2.png ,而不是 img.png )并且只将 main.xml.tmp 重命名为 main.xml 。然后删除 img.png
  3. 补充:如果您没有原子重命名,那么下一个最好的事情就是#2。每当您保存项目时,请为其指定一个新名称(例如 ver342.xml )。加载时,只需查找最新的一致(即其校验和验证)。保持2或3左右是安全的。如果已从较新的副本成功还原,则仅删除自动保存。