JavaRush /Курсы /C# SELF /Работа с файлами в C#: класс

Работа с файлами в C#: класс System.IO.File

C# SELF
35 уровень , 2 лекция
Открыта

1. Класс File: простота и эффективность

Класс System.IO.File воплощает концепцию "одна операция — один метод". Он предоставляет набор высокоуровневых методов, которые инкапсулируют сложную логику взаимодействия с операционной системой, позволяя вам сосредоточиться на своей задаче.

Что конкретно умеет класс File?

  • Запись текста или произвольных бинарных данных в файл.
  • Чтение содержимого файла целиком или по строкам (для текстовых файлов).
  • Копирование, удаление и переименование существующих файлов.
  • Проверка существования файла по заданному пути.
  • Работа с атрибутами файла (например, дата создания, размер – через сопутствующие классы).

Почему это удобно?
Все методы File — статические. Это значит, что для их использования не нужно создавать объект, достаточно просто вызвать нужный метод, например:

File.WriteAllText("data.txt", "Привет, файл!");

Такой стиль очень похож на работу с классом Console: всё просто и понятно, ничего не нужно «создавать», не нужно заботиться о потоках.

Если вам хочется иметь под рукой универсальный и удобный инструмент для работы с файлами, класс System.IO.File — именно такой. Он предлагает множество методов для чтения, записи, копирования, удаления и других основных операций с файлами, словно швейцарский нож для файловой системы.

Как использовать File?

Все методы класса File статические, то есть не нужно создавать объект, чтобы их использовать — просто пишете File.Что-то(). Это очень похоже на вызовы вроде Console.WriteLine(), только теперь для файлов.

string text = "Привет, мир!";
string path = "hello.txt";

// Запись текста в файл (если файл был, перезапишет!)
File.WriteAllText(path, text);

// Чтение всего файла в одну строку
string readText = File.ReadAllText(path);

Console.WriteLine("Содержимое файла: " + readText);

Лайфхак: Если вы 10 раз подряд пишете ReadAllText и WriteAllText, скорее всего, вы делаете что-то не так — эти методы хороши для простых сценариев и маленьких файлов.

Часто используемые методы класса File

Метод Описание
File.Exists(path)
Проверяет, существует ли файл по указанному пути
File.Delete(path)
Удаляет файл
File.Copy(src, dest)
Копирует файл
File.Move(src, dest)
Перемещает (или переименовывает) файл
File.ReadAllText(path)
Читает весь файл как одну строку
File.WriteAllText(path, t)
Записывает строку в файл (перезаписывает, если он есть)
File.ReadAllLines(path)
Читает все строки файла в массив строк
File.WriteAllLines(path, a)
Записывает массив строк в файл (каждая строка — отдельная)
File.ReadAllBytes(path)
Читает все байты файла в массив байт
File.WriteAllBytes(path, b)
Записывает массив байтов в файл

2. Запись и чтение текстовых файлов

Давайте рассмотрим наиболее часто используемые методы класса File с практическими примерами. Для работы с ними всегда требуется директива using System.IO; в начале вашего файла с кодом.

using System;
using System.IO; // Обязательно для работы с классом File
using System.Linq; // Может понадобиться для ReadLines

Чтение файла целиком

Это одна из самых частых задач: сохранение текстовых данных в файл или их загрузка обратно.

Запись одной строки (или всего текста) в файл: File.WriteAllText() Этот метод создает новый файл по указанному пути. Если файл с таким именем уже существует, его содержимое будет полностью перезаписано новым текстом.

string myFilePath = "notes.txt";
string textContent = "Сегодня прекрасный день для изучения C#.\nНе забудьте сделать домашнее задание.";

// Записываем весь текст в файл
File.WriteAllText(myFilePath, textContent); 
Console.WriteLine($"Файл '{myFilePath}' успешно создан/перезаписан.");

Чтение файла целиком в одну строку: File.ReadAllText() Этот метод считывает все содержимое текстового файла от начала до конца и возвращает его в виде одной большой строки.

string myFilePath = "notes.txt";

// Важно: всегда проверяйте существование файла перед чтением, чтобы избежать ошибок!
if (File.Exists(myFilePath))
{
    string content = File.ReadAllText(myFilePath);
    Console.WriteLine($"Содержимое файла '{myFilePath}':\n{content}");
}
else
{
    Console.WriteLine($"Ошибка: Файл '{myFilePath}' не найден для чтения.");
}

Важное замечание: Методы File.WriteAllText() и File.ReadAllText() отлично подходят для небольших и средних файлов (например, текстовые заметки, конфигурационные файлы, короткие логи). Но если файл очень большой (скажем, десятки или сотни мегабайт, а тем более гигабайты), загрузка всего его содержимого в одну строку в оперативную память может потреблять огромное количество памяти и даже привести к OutOfMemoryException. Для таких случаев существуют более эффективные подходы (о них ниже и в следующих лекциях).

3. Чтение файла построчно

Когда вы работаете с большими текстовыми файлами, такими как объемные CSV-отчеты, длинные файлы логов или текстовые базы данных, читать их целиком в память (то есть загрузить весь файл сразу из диска в оперативную память компьютера) становится неэффективным или даже невозможным. Здесь на помощь приходит File.ReadLines().

File.ReadLines(path) — этот метод не загружает весь файл сразу. Вместо этого он возвращает специальную коллекцию (IEnumerable<string>), которая позволяет вам читать файл строка за строкой по мере необходимости. Это называется "ленивым чтением" или "потоковым чтением".

string logFilePath = "application.log";
string text = """
INFO: Приложение запущено.
WARN: Обнаружена проблема в модуле X.
ERROR: Критическая ошибка сети.
INFO: Приложение остановлено.    
""";    
    
// Допустим, у нас есть лог-файл с множеством строк.
File.WriteAllText(logFilePath, text);

// Каждая итерация цикла считывает и обрабатывает только ОДНУ строку 
foreach (string line in File.ReadLines(logFilePath))
    Console.WriteLine($"- {line}");

Преимущество: Файл читается и обрабатывается по частям, что значительно снижает потребление памяти при работе с большими файлами.

Разница между File.ReadLines() и File.ReadAllLines():

  • File.ReadAllLines(path): Загружает все строки файла сразу в оперативную память и возвращает их в виде массива string[]. После вызова этого метода весь файл уже находится в памяти. Это удобно, если вам нужен полный доступ ко всем строкам одновременно (например, для сортировки или поиска).
  • File.ReadLines(path): Возвращает IEnumerable<string>, который позволяет перебирать строки файла по одной. Файл читается только тогда, когда вы запрашиваете следующую строку (например, в цикле foreach). Это более эффективно для очень больших файлов, когда вы не можете или не хотите загружать всё в память.

4. Запись массива строк

Иногда у вас есть список строк (например, из какой-то коллекции), и вы хотите записать каждую строку как отдельную запись в файл.

File.WriteAllLines(path, contents)

Этот метод принимает массив строк (или любую коллекцию строк) и записывает каждую строку как отдельную строку в файл. Как и WriteAllText(), он перезаписывает файл, если он уже существует.

string membersFilePath = "participants.txt";
string[] names = { "Иван Петров", "Мария Сидорова", "Сергей Козлов", "Анна Кузнецова" };

// Каждая строка массива будет записана как отдельная строка в файле
File.WriteAllLines(membersFilePath, names);

// Проверим содержимое
Console.WriteLine($"Содержимое '{membersFilePath}':");
foreach (string line in File.ReadLines(membersFilePath))
    Console.WriteLine($"- {line}");

5. Работа с бинарными файлами

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

Запись массива байтов: File.WriteAllBytes() Создает или перезаписывает файл, записывая в него весь предоставленный массив байтов.

string binaryDataPath = "settings.bin";
// Пример бинарных данных: картинка, видео, любые данные
byte[] settingsData = { 0x01, 0x0A, 0x2C, 0xFF, 0x00, 0x3F }; // Некие "сырые" настройки

File.WriteAllBytes(binaryDataPath, settingsData);
Console.WriteLine($"Бинарные данные записаны в файл '{binaryDataPath}'.");

Важно: если вы попробуете открыть 'settings.bin' в Блокноте, вы увидите нечитаемые символы ("кракозябры"), потому что эти байты не предназначены для интерпретации как текст.

Чтение массива байтов: File.ReadAllBytes() Считывает все содержимое бинарного файла в массив байтов.

if (File.Exists(binaryDataPath))
{
    byte[] readSettings = File.ReadAllBytes(binaryDataPath);
    Console.WriteLine($"Прочитано {readSettings.Length} байт из файла '{binaryDataPath}'.");

    Console.Write("Прочитанные байты (в шестнадцатеричном виде): ");
    foreach (byte b in readSettings)
        Console.Write($"{b:X2} "); // :X2 форматирует байт как двузначное шестнадцатеричное число

    Console.WriteLine();
}
else
{
    Console.WriteLine($"Ошибка: Бинарный файл '{binaryDataPath}' не найден.");
}

Эти методы очень удобны для сохранения небольших порций бинарных данных, например, для кэширования небольших изображений или сериализованных объектов.

6. Управление файлами на диске

Класс File также предоставляет методы для управления файлами на диске.

Проверка существования файла: File.Exists(path) Возвращает true, если файл по указанному пути существует, и false в противном случае. Крайне рекомендуется использовать этот метод перед попытками чтения, удаления или других операций, которые могут вызвать ошибку, если файл не найден.

string myFileToCheck = "report.txt";
// Сначала создадим его для примера
File.WriteAllText(myFileToCheck, "Пробный отчет.");

if (File.Exists(myFileToCheck))
{
    Console.WriteLine($"Файл '{myFileToCheck}' найден.");
    // Теперь можно безопасно читать, удалять и т.д.
}
else
{
    Console.WriteLine($"Файл '{myFileToCheck}' не найден.");
}

Удаление файла: File.Delete(path) безвозвратно удаляет файл с диска. Будьте крайне осторожны, это действие необратимо!

string fileToDelete = "temp_file.txt";
// Создадим файл для примера
File.WriteAllText(fileToDelete, "Этот файл будет удален.");

if (File.Exists(fileToDelete))
{
    File.Delete(fileToDelete);
    Console.WriteLine($"Файл '{fileToDelete}' успешно удален.");
}
else
{
    Console.WriteLine($"Файл '{fileToDelete}' уже не существует или был удален ранее.");
}

7. Исключения при работе с файлами

Работа с файлами всегда связана с взаимодействием с операционной системой и внешними ресурсами, что делает её потенциально уязвимой для ошибок.

Обрабатывайте исключения (ошибки)! При работе с файлами может произойти множество проблем, которые ваша программа должна уметь корректно обрабатывать, а не просто "падать". Типичные ситуации:

  • FileNotFoundException: Файл, к которому вы пытаетесь обратиться, не существует по указанному пути.
  • DirectoryNotFoundException: Папка, в которой вы пытаетесь создать или найти файл, не существует.
  • UnauthorizedAccessException: У вашей программы нет прав доступа для чтения, записи или удаления файла/папки (например, файл системный или находится в защищенной области).
  • IOException: Общая ошибка ввода/вывода, часто возникает, когда файл уже открыт другой программой и заблокирован, или нет места на диске.

Всегда используйте конструкцию try-catch для безопасной работы с файлами. Это позволяет "поймать" ошибку и предпринять соответствующие действия (например, вывести сообщение пользователю, записать в лог).

string sensitiveFilePath = "secret_data.txt";

try
{
    // Попытка прочитать файл, который, возможно, не существует или к которому нет доступа
    string secretContent = File.ReadAllText(sensitiveFilePath);
    Console.WriteLine($"Прочитано секретное содержимое: {secretContent}");
}
catch (FileNotFoundException) // Конкретная ошибка: файл не найден
{
    Console.WriteLine($"Ошибка: Файл '{sensitiveFilePath}' не найден. Убедитесь, что путь верен.");
}
catch (IOException ex) // Общая ошибка ввода/вывода (например, файл заблокирован)
{
    Console.WriteLine($"Ошибка ввода/вывода при работе с файлом '{sensitiveFilePath}': {ex.Message}");
}
catch (Exception ex) // Любая другая неожиданная ошибка
{
    Console.WriteLine($"Произошла непредвиденная ошибка: {ex.Message}");
}

8. Когда System.IO.File — не лучший выбор?

Хотя класс File удивительно универсален, существуют сценарии, где его использование не является оптимальным или даже возможным:

Огромные файлы (десятки+ гигабайт): Методы ReadAllText()/ReadAllBytes() загружают весь файл в память. Для экстремально больших файлов это приведет к OutOfMemoryException. В таких случаях File.ReadLines() (для текста) или прямой подход с потоками (Streams) (FileStream) необходим для чтения/записи по частям. (О потоках будет в следующей лекции).

Тонкий контроль над файловым доступом: Если вам нужен очень специфический контроль над тем, как файл открывается (например, эксклюзивный доступ, чтобы никто другой не мог его прочитать или записать, или наоборот, одновременный доступ для чтения несколькими процессами), File не предоставляет такой детализации. Для этого нужны более низкоуровневые классы потоков.

Асинхронные операции: Для высокопроизводительных приложений, которым нужно выполнять файловые операции, не блокируя основной поток (например, в веб-серверах), требуются асинхронные методы (например, ReadAllTextAsync()), которые также основаны на потоках.

Частичное чтение/запись: Если вам нужно прочитать только часть файла с определенного смещения или записать данные в середину существующего файла, методы File не подойдут. Для этого требуется прямой доступ к файловому потоку.

9. Резюме

Почему System.IO.File так популярен?

  • Простота и лаконичность: Его статические методы позволяют выполнять сложные операции буквально в одну-две строки кода. Это значительно упрощает разработку и делает код более читаемым.
  • Автоматическое управление ресурсами: File сам заботится об открытии и закрытии файлов для каждой операции, избавляя вас от необходимости вручную управлять ресурсами (что часто приводит к ошибкам).
  • Универсальность для повседневных задач: Для большинства типовых операций с текстовыми и бинарными файлами малых и средних размеров возможностей File более чем достаточно.
  • Распространенность: В большинстве практических задач и на собеседованиях ожидается, что вы умеете уверенно пользоваться методами класса File.
  • Скрытая сложность: Все методы написаны таким образом, чтобы инкапсулировать сложную логику взаимодействия с операционной системой, предоставляя вам чистый и понятный интерфейс.

Частые Вопросы и Уточнения

Какой максимальный размер файла допустим для File.ReadAllText()?
Для файлов размером до нескольких десятков мегабайт (< 100 МБ) обычно никаких проблем не возникает. Если файл больше (сотни МБ, гигабайты), использование File.ReadAllText() может привести к исчерпанию оперативной памяти (исключение OutOfMemoryException). В таких случаях следует использовать File.ReadLines() для построчного чтения или более продвинутые инструменты для потоковой обработки, о которых вы узнаете позже.

Можно ли создать/записать файл в папке, которой нет?
Нет. Методы File.WriteAllText(), File.WriteAllBytes() и другие не создают родительские каталоги автоматически. Если вы указываете путь C:\MyAppData\Logs\current.log, а папки MyAppData или Logs не существует, возникнет DirectoryNotFoundException. Сначала вам нужно убедиться, что все необходимые папки существуют, используя методы класса System.IO.Directory (например, Directory.CreateDirectory("myfolder")).

Как узнать дату создания, изменения или размер файла?
Для получения более детальной информации о файле, такой как дата создания, дата последнего изменения, размер, атрибуты (скрытый, только для чтения и т.д.), используйте класс System.IO.FileInfo. Он предоставляет множество полезных свойств.

2
Задача
C# SELF, 35 уровень, 2 лекция
Недоступна
Создание и запись данных в файл
Создание и запись данных в файл
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ