Когда можно проверить, существует ли файл?
Файловые системы нестабильны. Это означает, что вы не можете доверять результату одной операции, чтобы он был действительным для следующей, даже если это следующая строка кода. Ты не можешь просто сказать if (some file exists and I have permissions for it) open the file
и ты не можешь сказать if (some file does not exist) create the file
, Всегда есть вероятность, что результат вашего if
условие будет меняться между двумя частями вашего кода. Операции разные: не атомарные.
Что еще хуже, характер проблемы означает, что, если у вас возникнет искушение сделать эту проверку, скорее всего, вы уже обеспокоены или знаете, что что-то, что вы не контролируете, может произойти с файлом. Природа сред разработки делает это событие менее вероятным во время тестирования и очень трудно воспроизводится. Так что не только у вас есть ошибка, но ошибка не будет отображаться во время тестирования.
Поэтому при нормальных обстоятельствах лучший способ действий - даже не пытаться проверить, существует ли файл или каталог. Вместо этого уделите время разработки обработке исключений из файловой системы. В любом случае вы должны обработать эти исключения, так что это намного лучше использует ваши ресурсы. Несмотря на то, что исключения являются медленными, проверка существования файла требует дополнительной поездки на диск, а доступ к диску намного медленнее. У меня даже есть ответ с хорошим голосом на этот счет в другом вопросе.
Но у меня есть некоторые сомнения. В.Net, например, если это действительно всегда так, .Exists()
методы не были бы в API в первую очередь. Также рассмотрите сценарии, в которых ваша программа нуждается в создании файла. Первый пример, который приходит на ум, касается настольного приложения. Это приложение устанавливает файл конфигурации пользователя по умолчанию в свой домашний каталог, и при первом запуске каждого пользователя приложение копирует этот файл в папку данных приложения этого пользователя. Он ожидает, что файл не существует при первом запуске.
Итак, когда допустимо заранее проверить наличие (или другие атрибуты, такие как размер и разрешения) файла? Является ли ожидание неудачи, а не успеха с первой попытки достаточно хорошим эмпирическим правилом?
18 ответов
Метод File.Exists существует главным образом для проверки существования файла, когда вы не собираетесь открывать файл. Например, проверка на наличие блокирующего файла, само существование которого говорит вам что-то, но чье содержимое не имеет значения.
Если вы собираетесь открыть файл, вам нужно будет обработать любое исключение независимо от результатов любых предыдущих вызовов File.Exists. Так что, как правило, в этих обстоятельствах нет смысла называть это. Просто используйте соответствующее значение перечисления FileMode в вашем методе open и обработайте любые исключения, такие простые.
РЕДАКТИРОВАТЬ: Несмотря на то, что это сформулировано в терминах.Net API, он основан на базовом системном API. Как Windows, так и Unix имеют системные вызовы (т.е. CreateFile), которые используют эквивалент перечисления FileMode. На самом деле в.Net (или Mono) значение FileMode просто передается базовому системному вызову.
Как правило, такие методы, как File.Exists
или свойства типа WeakReference.Alive
или же SomeConcurrentQueue.Count
не являются полезными в качестве средства обеспечения существования "хорошего" состояния, но могут быть полезны в качестве средства определения того, что "плохое" состояние существует, не выполняя никакой ненужной (и, возможно, контрпродуктивной) работы. Такие ситуации могут возникать во многих сценариях, связанных с блокировками (и файлами, поскольку они часто включают в себя блокировки). Поскольку все подпрограммы, которым необходимо заблокировать набор ресурсов, должны, когда это практически возможно, всегда получать блокировки этих ресурсов в согласованном порядке, может потребоваться установить блокировку одного ресурса, который, как ожидается, существует, до получения ресурса, который может или может не существовать. В таком случае, хотя невозможно избежать возможности того, что кто-то может заблокировать первый ресурс, не сможет получить второй, а затем снять первую блокировку, не выполнив с ней никакой полезной работы, проверяя существование второго ресурса перед получение блокировки на первом минимизирует ненужные и бесполезные усилия.
Это зависит от ваших требований, но один из способов - попытаться получить эксклюзивный дескриптор открытого файла с каким-то механизмом повторных попыток. Если у вас есть этот дескриптор, другому процессу будет трудно (или невозможно) удалить (или переместить) этот файл.
Я использовал код в.NET, похожий на следующий, чтобы получить эксклюзивный дескриптор файла, где я ожидаю, что какой-то другой процесс, возможно, будет писать файл:
FileInfo fi = new FileInfo(fullFilePath);
int attempts = maxAttempts;
do
{
try
{
// Asking to open for reading with exclusive access...
fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.None);
}
// Ignore any errors...
catch {}
if (fs != null)
{
break;
}
else
{
Thread.Sleep(100);
}
}
while (--attempts > 0);
Один пример: вы можете проверить наличие файлов, которые вы не можете открыть (например, из-за разрешений).
Другой, возможно, лучший пример: вы хотите проверить наличие файла устройства Unix. Но определенно не открывайте его; открытие имеет побочные эффекты (например, открыть / закрыть /dev/st0
перемотаем ленту)
В среде *nix хорошо зарекомендовавший себя способ проверки того, что другая копия программы уже запущена, заключается в создании файла блокировки. Таким образом, проверка существования файла используется для проверки этого.
Я думаю, что проверка имеет смысл, когда вы хотите убедиться, что файл был там в первую очередь. Как вы сказали, файлы настроек... если есть файл, я попытаюсь объединить существующие настройки вместо того, чтобы сносить их.
В других случаях пользователь говорит мне сделать что-то с файлом. Да, я знаю, openFileDialog проверит, существует ли файл (но это не обязательно). Я смутно помню, что еще в VB6 это было не так, поэтому проверка файла существовала, и мне сказали, что это обычное дело.
Я бы предпочел не программировать по исключениям.
редактировать
Я не упустил суть. Вы можете попытаться получить доступ к файлу, возникнет исключение, а затем, когда вы приступите к созданию файла, файл уже был помещен туда. Что теперь заставляет ваш код обработки исключений идти на фритюре. Так что, я думаю, мы могли бы иметь обработчик исключений в нашем обработчике исключений, чтобы поймать, что файл снова изменился...
Я бы лучше попытался предотвратить исключения, а не использовать их для управления логикой.
редактировать
Кроме того, еще один раз, чтобы проверить такие атрибуты, как размер, - это когда вы ожидаете завершения файловой операции, да, вы никогда не знаете наверняка, но с хорошим алгоритмом и в зависимости от системы, записывающей файл, вы могли бы справиться с большим количеством случаи (система работала в течение пяти лет, которая наблюдала за небольшими файлами, поступающими через ftp, и она использует тот же API-интерфейс, что и наблюдатель файловой системы, а затем начинает опрос, ожидая, когда файл перестанет изменяться, прежде чем вызывать событие, когда файл готов к употреблению).
Это может быть слишком упрощенно, но я думаю, что основной причиной проверки существования файла (отсюда и существование.Exists()) будет предотвращение непреднамеренной перезаписи существующих файлов, а не исключение, вызванное попыткой доступа несуществующие или недоступные файлы.
РЕДАКТИРОВАТЬ 2
Это было на самом деле слишком упрощенно, и я рекомендую вам посмотреть ответ Стивена Мартина.
Я бы проверил его, только если я ожидаю, что он отсутствует (например, настройки приложения) и только если мне нужно прочитать файл.
Если мне нужно записать в файл, это либо файл журнала (так что я могу просто добавить его или создать новый), либо я заменяю его содержимое, так что я все равно могу его воссоздать.
Если я ожидаю, что файл существует, было бы правильно, если выдается исключение. Обработка исключений должна информировать пользователя или выполнять восстановление. Мое мнение таково, что это приводит к более чистому коду.
Защита файлов (т. Е. Не перезапись (возможно, важных) файлов) отличается, в этом случае я бы всегда проверял, существует ли файл, если инфраструктура не делает этого для меня (подумайте SaveFileDialog)
Чтобы ответить на мой собственный вопрос (частично), я хочу расширить пример, который я использовал: файл конфигурации по умолчанию.
Вместо того, чтобы проверять, существует ли он при запуске приложения, и пытаться скопировать файл, если проверка не удалась, нужно всегда пытаться скопировать файл. Вы просто делаете это таким образом, что копия потерпит неудачу, если файл существует, а не заменит существующий файл. Таким образом, все, что вам нужно сделать, это перехватить и проигнорировать любое исключение, выданное в случае сбоя копирования из-за существующего файла.
У нас есть диагностический инструмент, который должен собрать набор файлов, включая журнал установки. В зависимости от различных условий журнал установщика может находиться в одной из двух папок. Хуже того, в обеих этих папках могут быть разные версии журнала. Как инструмент находит правильный?
Это довольно просто, если вы проверите на существование. Если присутствует только один, захватите этот файл. Если два существует, найдите, который имеет самое позднее время модификации и захватите этот файл. Это просто нормальный способ делать вещи.
Разнообразие приложений включает в себя встроенные веб-серверы. Для них характерно генерировать самозаверяющие сертификаты SSL при первом запуске. Простой способ реализовать это - проверить, существует ли сертификат при запуске, и создать его, если нет.
Теоретически, он может существовать для проверки и не существовать позже. В этом случае мы получим ошибку, когда попытаемся прослушать, но это может быть легко обработано и не имеет большого значения.
Также возможно, что он не существует для проверки и существует позже. В этом случае он либо перезаписывается новым сертификатом, либо запись нового сертификата завершается неудачно, в зависимости от вашей политики. Первое немного раздражает, с точки зрения изменения сертификата, вызывающего некоторую тревогу, но также не очень критичного, особенно если вы делаете небольшую запись в журнал, чтобы указать, что происходит.
И на практике оба случая чрезвычайно маловероятны.
Существует множество возможных приложений, которые вы вполне можете написать, что простого File.Exists более чем достаточно для данной работы. Если это конфигурационный файл, который будет использоваться только вашим приложением, то вам не нужно слишком напрягаться при обработке исключений.
Хотя "недостатки", которые вы указали при использовании этого метода, являются действительными, это не значит, что они не являются приемлемыми недостатками в некоторых ситуациях.
Я думаю, что причина "Exists" состоит в том, чтобы определить, когда файлы отсутствуют, без необходимости создавать все служебные данные ОС, необходимые для доступа к файлу или создания исключений. Так что это оптимизация обработки файлов больше всего на свете.
Для одного файла сохранение, которое дает "Exists", обычно незначительно. Если вы проверяли, существует ли файл много-много раз (например, при поиске файлов #include), экономия может быть значительной.
В.Net спецификация для File.Exists не перечисляет никаких исключений, которые может вызвать метод, в отличие от, например, File.Open, в котором перечисляются девять исключений, так что в первом случае, безусловно, меньше проверок.
Даже если "Exists" возвращает true, вам все равно нужно обрабатывать исключения при открытии файла, как предполагает ссылка.Net.
Я думаю, что в любое время, когда вы знаете, что файл может существовать или не существовать, и вы хотите выполнить какое-то альтернативное действие, основанное на существовании файла, вы должны выполнить проверку, потому что в этом случае это не исключительное условие, что файл не существует, Это не освобождает вас от необходимости обрабатывать исключения - от кого-либо другого, удаляющего или создающего файл между проверкой и вашим открытием - но это проясняет цель программы и не полагается на обработку исключений для выполнения потока -контрольная логика.
РЕДАКТИРОВАТЬ: Пример может быть ротация журнала при запуске.
try
{
if (File.Exists("app.log"))
{
RotateLogs();
}
log = File.Open("app.log", FileMode.CreateNew );
}
catch (IOException)
{
...another writer, perhaps?
}
catch (UnauthorizedAccessException)
{
...maybe I should have used runas?
}
Как вы указали, всегда важно, что должна делать программа, если файл отсутствует. Во всех моих приложениях пользователь всегда может удалить файл конфигурации, и приложение создаст новый файл со значениями по умолчанию. Нет проблем. Я также отправляю свои приложения без конфигурационных файлов.
Но пользователи склонны удалять файлы и даже файлы, которые они не должны удалять, как серийные ключи и файлы шаблонов. Я всегда проверяю эти файлы, потому что без них приложение не может работать вообще. Я не могу создать новый серийный ключ по умолчанию.
Что должно произойти, если файл отсутствует? Вы можете выполнить поиск файла или обработчик исключений, но реальный вопрос: что произойдет, если файл отсутствует? Или насколько важен файл для приложения. Я проверяю все время, прежде чем пытаться получить доступ к любым файлам поддержки для приложения. Дополнительно я делаю обработку ошибок, если файл поврежден и не может быть загружен.
Ваша проблема может быть легко решена с помощью базовой информатики... читайте на Семафорах.
(Я не хотел звучать как придурок, я просто указывал вам простой ответ на общую проблему).
Если вы беспокоитесь о том, чтобы кто-то другой удалил файл, возможно, вам следует внедрить какую-то систему блокировки. Например, я работал над кодом для C-News, сервера новостей Usenet. Поскольку многое из того, что он делал, могло происходить асинхронно, он "блокировал" файл или каталог, создавая временный файл, а затем жестко связывал его с файлом с именем "LOCK". Если ссылка не удалась, это означало бы, что какая-то другая версия программы писала в этот каталог, иначе она была бы вашей, и вы могли бы делать то, что вам нравится.
Отличная вещь в этом заключается в том, что большая часть программы была написана на shell и awk, и это был очень переносимый механизм блокировки. Кроме того, файл блокировки будет содержать PID владельца, поэтому вы можете посмотреть на существующий файл блокировки, чтобы убедиться, что владелец все еще работает.
Хотя это пост, не зависящий от языка, похоже, вы говорите о.NET. Большинство систем (.NET и другие) имеют более подробные API, чтобы выяснить, существует ли файл при открытии файла.
Что вам нужно сделать, это позвонить, чтобы получить доступ к файлу, поскольку он обычно указывает на какую-то ошибку, что файл не существует (если он действительно не существует). В.NET вам придется пройти через слой P/Invoke и использовать функцию API CreateFile. Если эта функция возвращает ошибку ERROR_FILE_NOT_FOUND, то вы знаете, что файл не существует. Если он успешно возвращается, у вас есть дескриптор, который вы можете использовать.
Дело в том, что это несколько атомарная операция, которая, в конечном счете, является тем, что вы ищете.
Затем с помощью дескриптора вы можете передать его конструктору FileStream и выполнить свою работу над файлом.