重用文件流

在过去,我总是使用FileStream对象来写或重写整个文件,之后我会立即关闭流。 但是,现在我正在开发一个程序,我想在其中保持FileStream打开,以便允许用户在两​​次保存期间保持对文件的访问。 (见我之前的问题 )。

我正在使用XmlSerializer将我的类序列化为from和XML文件。 但是现在我保持FileStream打开以便稍后用于保存(重新序列化)我的类实例。 如果我重复使用相同的文件流而不是使用新的文件流,是否需要进行任何特殊考虑? 我是否需要在保存之间将流重置为开头? 如果稍后保存的大小小于先前的保存,则FileStream将保留旧文件中的剩余字节,从而创建损坏的文件? 我是否需要做一些事情来清除文件,以便它表现得好像我每次都在写一个全新的文件?

您的怀疑是正确的 – 如果您重置打开文件流的位置并写入比文件中已有的内容小的内容,它将留下尾随数据并导致损坏的文件(当然,取决于您对“损坏”的定义) )。

如果要覆盖该文件,您应该在完成后关闭该流,并在准备好重新保存时创建新流。

我从您的链接问题中注意到您将文件保持打开状态,以防止其他用户同时写入该文件。 这可能不是我的选择,但是如果你要这样做,那么我认为你可以通过在连续保存之间调用stream.SetLength(0)来“清除”该文件。

有多种方法可以做到这一点; 如果要重新打开文件,可能将其设置为截断:

 using(var file = new FileStream(path, FileMode.Truncate)) { // write } 

如果您在打开文件时覆盖文件,则只需在编写后修剪它:

 file.SetLength(file.Position); // assumes we're at the new end 

我会尽量避免删除/重新创建,因为这会丢失任何ACL等。

另一个选项可能是在开始重写文件之前使用SetLength(0)截断文件。

最近遇到了同样的要求。 事实上,以前,我曾经在using语句中创建一个新的FileStream并覆盖以前的文件。 看起来像简单有效的事情。

 using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write) { ProtoBuf.Serializer.Serialize(stream , value); } 

但是,我遇到了锁定问题,其他一些进程正在锁定目标文件。 在我试图阻止这种情况时,我在将错误推到堆栈之前多次重试了写入。

 int attempt = 0; while (true) { try { using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write) { ProtoBuf.Serializer.Serialize(stream , value); } break; } catch (IOException) { // could be locked by another process // make up to X attempts to write the file attempt++; if (attempt >= X) { throw; } Thread.Sleep(100); } } 

这似乎适用于几乎所有人。 然后问题机器出现了,并迫使我沿着整个时间保持文件锁定的道路。 因此,在它已被锁定的情况下,为了代替重试写入文件,我现在确保我得到并保持流打开,以便以后的写入没有锁定问题。

 int attempt = 0; while (true) { try { _stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); break; } catch (IOException) { // could be locked by another process // make up to X attempts to open the file attempt++; if (attempt >= X) { throw; } Thread.Sleep(100); } } 

现在当我写文件时, FileStream位置必须重置为零,正如Aaronaught所说。 我选择通过调用_stream.SetLength(0)来“清除”文件。 似乎是最简单的选择。 然后使用我们选择的序列化器,Marc Gravell的protobuf-net ,将值序列化为流。

 _stream.SetLength(0); ProtoBuf.Serializer.Serialize(_stream, value); 

这在大多数情况下工作得很好,文件完全写入磁盘。 但是,有几次我发现文件没有立即写入磁盘。 为确保刷新流并将文件完全写入磁盘,我还需要调用_stream.Flush(true)

 _stream.SetLength(0); ProtoBuf.Serializer.Serialize(_stream, value); _stream.Flush(true); 

基于您的问题,我认为您可以更好地关闭/重新打开基础文件。 除了编写整个文件之外,您似乎没有做任何事情。 您可以通过重写Open / Close / Flush / Seek来添加的值将接近于0.专注于您的业务问题。