Повторное использование файлового потока - PullRequest
17 голосов
/ 22 февраля 2010

В прошлом я всегда использовал объект FileStream для записи или перезаписи всего файла, после чего я немедленно закрывал поток. Однако сейчас я работаю над программой, в которой я хочу оставить FileStream открытым, чтобы позволить пользователю сохранить доступ к файлу, пока он работает между сохранениями. (См. Мой предыдущий вопрос ).

Я использую XmlSerializer для сериализации моих классов в файл from и XML. Но теперь я оставляю FileStream открытым для последующего сохранения (повторной сериализации) экземпляра моего класса. Есть ли какие-то особые соображения, которые мне нужно учитывать, если я снова и снова использую один и тот же поток файлов, а не использую новый поток файлов? Нужно ли сбрасывать поток в начало между сохранениями? Если при последующем сохранении размер меньше, чем при предыдущем сохранении, FileStream оставит оставшиеся байты из старого файла и, таким образом, создаст поврежденный файл? Нужно ли что-то делать, чтобы очистить файл, чтобы он вел себя так, как будто я пишу совершенно новый файл каждый раз?

Ответы [ 5 ]

14 голосов
/ 22 февраля 2010

Ваше подозрение верное - если вы сбросите позицию открытого файлового потока и напишите содержимое, которое меньше, чем то, что уже находится в файле, оно оставит завершающие данные и приведет к повреждению файла (в зависимости от вашего определения «поврежден»). конечно).

Если вы хотите перезаписать файл, вы действительно должны закрыть поток, когда закончите с ним, и создать новый поток, когда будете готовы к повторному сохранению.

Из вашего связанного вопроса я замечаю, что вы держите файл открытым, чтобы другие пользователи не могли писать в него одновременно. Вероятно, это не мой выбор, но если вы собираетесь это сделать, то я думаю вы можете "очистить" файл, вызывая stream.SetLength(0) между последовательными сохранениями.

10 голосов
/ 22 февраля 2010

Существуют различные способы сделать это; если вы заново открываете файл, возможно, установите для него усечение:

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

Если вы перезаписываете файл, когда он уже открыт, просто обрежьте его после записи:

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

Я бы попытался избежать удаления / воссоздания, так как при этом теряются все ACL и т. Д.

7 голосов
/ 22 февраля 2010

Другим вариантом может быть использование SetLength (0) для обрезки файла перед тем, как вы начнете переписывать его.

1 голос
/ 13 апреля 2013

Недавно столкнулся с тем же требованием. Фактически ранее я использовал для создания нового FileStream внутри оператора using и перезаписи предыдущего файла. Похоже на простую и эффективную вещь.

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 должна быть сброшена в ноль, как сказал Аарона. Я решил «очистить» файл, вызвав _stream.SetLength(0). Казалось, самый простой выбор. Затем с помощью нашего выбранного сериализатора, protobuf-net Марка Грэвелла, сериализуйте значение в поток.

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

В большинстве случаев это прекрасно работает, и файл полностью записывается на диск. Однако в некоторых случаях я наблюдал, что файл не сразу записывается на диск. Чтобы поток был очищен и файл был полностью записан на диск, мне также нужно было вызвать _stream.Flush(true).

_stream.SetLength(0);
ProtoBuf.Serializer.Serialize(_stream, value);
_stream.Flush(true);
0 голосов
/ 22 февраля 2010

Исходя из вашего вопроса, я думаю, вам лучше будет закрыть / заново открыть базовый файл. Вы, кажется, не делаете ничего, кроме записи всего файла. Значение, которое вы можете добавить, переписав Open / Close / Flush / Seek, будет рядом с 0. Сконцентрируйтесь на своей бизнес-проблеме.

...