Основы работы с файлами и потоками в C#

от автора

Привет, Хабр!

Работа с файлами — неотъемлемая часть многих проектов. Простая запись логов, чтение конфигурационных файлов или обработка больших данных — все это является частью взаимодействовия с файловой системой.

В этой статье, мы рассмотрим основы работы с файлами и потоками в C#.

Основные классы для работы с файлами и потоками в C#

File и FileInfo — это основные классы в пространстве имен System.IO для работы с файловой системой. Оба класса предоставляют методы для создания, копирования, удаления, перемещения и открытия файлов, но есть пару различий.

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

Пример File:

// создание нового файла string filePath = @"C:\example.txt"; File.Create(filePath).Close();  // проверка существования файла if (File.Exists(filePath)) {     Console.WriteLine("Файл существует."); }  // запись текста в файл string content = "Hello, World!"; File.WriteAllText(filePath, content);  // чтение текста из файла string readContent = File.ReadAllText(filePath); Console.WriteLine(readContent);  // копирование файла string copyPath = @"C:\example_copy.txt"; File.Copy(filePath, copyPath);  // перемещение файла string movePath = @"C:\example_moved.txt"; File.Move(copyPath, movePath);  // удаление файла File.Delete(movePath);

FileInfo сохраняет состояние файла, что делает его более хорошим при выполнении нескольких операций на одном и том же файле.

Пример:

// создание экземпляра FileInfo FileInfo fileInfo = new FileInfo(filePath);  // создание нового файла using (FileStream fs = fileInfo.Create()) {     byte[] info = new UTF8Encoding(true).GetBytes("This is some text in the file.");     fs.Write(info, 0, info.Length); }  // проверка существования файла if (fileInfo.Exists) {     Console.WriteLine("Файл существует."); }  // запись текста в файл using (StreamWriter writer = fileInfo.AppendText()) {     writer.WriteLine("Добавленный текст."); }  // чтение текста из файла using (StreamReader reader = fileInfo.OpenText()) {     string s = "";     while ((s = reader.ReadLine()) != null)     {         Console.WriteLine(s);     } }  // копирование файла string copyFileInfoPath = @"C:\example_copy_info.txt"; fileInfo.CopyTo(copyFileInfoPath);  // перемещение файла string moveFileInfoPath = @"C:\example_moved_info.txt"; fileInfo.MoveTo(moveFileInfoPath);  // удаление файла fileInfo.Delete();

FileStream — используется для работы с файлами на более низком уровне.

Пример FileStream:

string fileStreamPath = @"C:\filestream_example.txt";  // создание и запись в файл using (FileStream fs = new FileStream(fileStreamPath, FileMode.Create)) {     byte[] data = Encoding.UTF8.GetBytes("Hello, FileStream!");     fs.Write(data, 0, data.Length); }  // чтение из файла using (FileStream fs = new FileStream(fileStreamPath, FileMode.Open, FileAccess.Read)) {     byte[] data = new byte[fs.Length];     int numBytesToRead = (int)fs.Length;     int numBytesRead = 0;      while (numBytesToRead > 0)     {         int n = fs.Read(data, numBytesRead, numBytesToRead);          if (n == 0)             break;          numBytesRead += n;         numBytesToRead -= n;     }      string text = Encoding.UTF8.GetString(data);     Console.WriteLine(text); }  // асинхронное чтение и запись async Task WriteAsync(string path, string content) {     byte[] encodedText = Encoding.UTF8.GetBytes(content);     using (FileStream sourceStream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.None, 4096, true))     {         await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);     }; }  async Task<string> ReadAsync(string path) {     using (FileStream sourceStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))     {         byte[] buffer = new byte[1024];         int numRead;         StringBuilder sb = new StringBuilder();         while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)         {             string text = Encoding.UTF8.GetString(buffer, 0, numRead);             sb.Append(text);         }         return sb.ToString();     } }  // пример использования асинхронного метода await WriteAsync(fileStreamPath, "Async Hello, FileStream!"); string asyncContent = await ReadAsync(fileStreamPath); Console.WriteLine(asyncContent);

Directory и DirectoryInfo предоставляют методы для создания, удаления и перечисления содержимого директорий. Как и File и FileInfo, Directory — это статический класс, а DirectoryInfo — это экземплярный класс.

Пример Directory:

// создание новой директории string directoryPath = @"C:\example_dir"; Directory.CreateDirectory(directoryPath);  // проверка существования директории if (Directory.Exists(directoryPath)) {     Console.WriteLine("Директория существует."); }  // перечисление файлов в директории string[] files = Directory.GetFiles(directoryPath); foreach (string file in files) {     Console.WriteLine(file); }  // перечисление поддиректорий string[] directories = Directory.GetDirectories(directoryPath); foreach (string dir in directories) {     Console.WriteLine(dir); }  // удаление директории Directory.Delete(directoryPath, true);

ПримерDirectoryInfo:

// создание экземпляра DirectoryInfo DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath);  // создание новой директории directoryInfo.Create();  // проверка существования директории if (directoryInfo.Exists) {     Console.WriteLine("Директория существует."); }  // перечисление файлов в директории FileInfo[] fileInfos = directoryInfo.GetFiles(); foreach (FileInfo fileInfo in fileInfos) {     Console.WriteLine(fileInfo.Name); }  // перечисление поддиректорий DirectoryInfo[] directoryInfos = directoryInfo.GetDirectories(); foreach (DirectoryInfo dirInfo in directoryInfos) {     Console.WriteLine(dirInfo.Name); }  // удаление директории directoryInfo.Delete(true);

Чтение и запись файлов с FileStream

Создание и открытие файлов с помощью FileStream включает использование различных режимов FileMode, доступа FileAccess и совместного использования FileShare.

Примеры создания и открытия файлов:

// создание нового файла string path = @"C:\example.txt"; using (FileStream fs = new FileStream(path, FileMode.Create)) {     byte[] info = new UTF8Encoding(true).GetBytes("This is some text in the file.");     fs.Write(info, 0, info.Length); }  // открытие существующего файла для чтения using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) {     byte[] b = new byte[1024];     UTF8Encoding temp = new UTF8Encoding(true);     while (fs.Read(b, 0, b.Length) > 0)     {         Console.WriteLine(temp.GetString(b));     } }

FileMode.Create используется для создания нового файла, который будет перезаписан, если он уже существует. FileMode.Open открывает существующий файл, если он не существует, будет выброшено исключение FileNotFoundException.

Чтение данных:

Метод Read используется для чтения байтов из потока и записи их в заданный буфер.

using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) {     byte[] buffer = new byte[1024];     int bytesRead;     while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)     {         Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, bytesRead));     } }

Запись данных:

Метод Write используется для записи массива байтов в файл.

using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write)) {     byte[] info = new UTF8Encoding(true).GetBytes("More text to add to the file.");     fs.Write(info, 0, info.Length); }

Работа с различными режимами доступа и совместного использования:

FileAccess определяет уровень доступа к файлу: только чтение FileAccess.Read, только запись FileAccess.Write, или чтение и запись FileAccess.ReadWrite.

FileShare контролирует доступ других потоков к файлу: например, FileShare.Read позволяет другим потокам читать файл, но не записывать в него.

// открытие файла для чтения и записи, с возможностью одновременного чтения другими потоками using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read)) {     // чтение данных     byte[] buffer = new byte[1024];     int bytesRead = fs.Read(buffer, 0, buffer.Length);     Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, bytesRead));      // запись данных     byte[] info = new UTF8Encoding(true).GetBytes("Additional data");     fs.Write(info, 0, info.Length); }

Так можно юзать FileStream с FileAccess.ReadWrite для одновременного чтения и записи, при этом позволяя другим потокам читать файл с помощью FileShare.Read.

Работа с потоками данных в C#

В .NET Stream представляет собой абстрактный класс, который предоставляет универсальный интерфейс для чтения и записи байтов. Все потоки данных в .NET наследуются от Stream, включая FileStream, MemoryStream, NetworkStream и другие.

Основные методы Stream:

  • Read(byte[] buffer, int offset, int count): читает байты из потока и записывает их в буфер.

  • Write(byte[] buffer, int offset, int count): записывает байты из буфера в поток.

  • Seek(long offset, SeekOrigin origin): перемещает текущую позицию в потоке.

  • Flush(): очищает все буферы для текущего потока.

Пример использования Stream:

using System; using System.IO;  class Program {     static void Main()     {         string path = "example.txt";          using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))         {             byte[] info = new UTF8Encoding(true).GetBytes("Hello, Stream!");             fs.Write(info, 0, info.Length);         }          using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))         {             byte[] b = new byte[1024];             UTF8Encoding temp = new UTF8Encoding(true);             while (fs.Read(b, 0, b.Length) > 0)             {                 Console.WriteLine(temp.GetString(b));             }         }     } }

StreamReader и StreamWriter являются классами для работы с текстовыми данными.

Пример использования StreamWriter:

using System; using System.IO;  class Program {     static void Main()     {         string path = "example.txt";          using (StreamWriter sw = new StreamWriter(path))         {             sw.WriteLine("Hello, StreamWriter!");         }          using (StreamReader sr = new StreamReader(path))         {             string line;             while ((line = sr.ReadLine()) != null)             {                 Console.WriteLine(line);             }         }     } }

BinaryReader и BinaryWriter используются для чтения и записи данных в бинарном формате. Эти классы хороши для работы с примитивными типами данных.

Пример:

using System; using System.IO;  class Program {     static void Main()     {         string path = "example.bin";          using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create)))         {             writer.Write(1.25);             writer.Write("Hello, BinaryWriter!");             writer.Write(true);         }          using (BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open)))         {             Console.WriteLine(reader.ReadDouble());             Console.WriteLine(reader.ReadString());             Console.WriteLine(reader.ReadBoolean());         }     } }

Асинхронные операции позволяют выполнять задачи ввода-вывода без блокировки основного потока.

Пример асинхронной записи с StreamWriter:

using System; using System.IO; using System.Text; using System.Threading.Tasks;  class Program {     static async Task Main()     {         string path = "example.txt";          using (StreamWriter sw = new StreamWriter(path, true, Encoding.UTF8))         {             await sw.WriteLineAsync("Hello, Async StreamWriter!");         }          using (StreamReader sr = new StreamReader(path))         {             string line;             while ((line = await sr.ReadLineAsync()) != null)             {                 Console.WriteLine(line);             }         }     } }

Мониторинг изменений

FileSystemWatcher позволяет следить за созданием, изменением, удалением и переименованием файлов и директорий.

Для начала создадим экземпляр FileSystemWatcher, указав директорию для мониторинга:

using System; using System.IO;  public class FileSystemWatcherExample {     private FileSystemWatcher _watcher;      public FileSystemWatcherExample(string path)     {         _watcher = new FileSystemWatcher(path);         _watcher.IncludeSubdirectories = true; // следить за подкаталогами         _watcher.Filter = "*.*"; // следить за всеми файлами         _watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName |                                 NotifyFilters.Attributes | NotifyFilters.Size |                                 NotifyFilters.LastWrite | NotifyFilters.LastAccess |                                 NotifyFilters.CreationTime | NotifyFilters.Security;          // Подписка на события         _watcher.Created += OnCreated;         _watcher.Deleted += OnDeleted;         _watcher.Changed += OnChanged;         _watcher.Renamed += OnRenamed;         _watcher.Error += OnError;          _watcher.EnableRaisingEvents = true; // включение мониторинга     }      private void OnCreated(object sender, FileSystemEventArgs e) =>          Console.WriteLine($"File created: {e.FullPath}");      private void OnDeleted(object sender, FileSystemEventArgs e) =>          Console.WriteLine($"File deleted: {e.FullPath}");      private void OnChanged(object sender, FileSystemEventArgs e) =>          Console.WriteLine($"File changed: {e.FullPath}");      private void OnRenamed(object sender, RenamedEventArgs e) =>          Console.WriteLine($"File renamed: {e.OldFullPath} to {e.FullPath}");      private void OnError(object sender, ErrorEventArgs e) =>          PrintException(e.GetException());      private void PrintException(Exception ex)     {         if (ex != null)         {             Console.WriteLine($"Message: {ex.Message}");             Console.WriteLine("Stacktrace:");             Console.WriteLine(ex.StackTrace);             PrintException(ex.InnerException);         }     } }

Чтобы более точно настроить, какие изменения отслеживать, есть свойства Filter и NotifyFilter:

  • Filter позволяет отслеживать изменения только определенных типов файлов, например, только текстовых файлов *.txt.

  • NotifyFilter позволяет указывать типы изменений.

Пример настройки фильтров:

_watcher.Filter = "*.txt"; _watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Size | NotifyFilters.LastWrite;

События FileSystemWatcher включают Created, Deleted, Changed, Renamed и Error. Примеры обработки этих событий:

private void OnCreated(object sender, FileSystemEventArgs e) =>      Console.WriteLine($"File created: {e.FullPath}");  private void OnDeleted(object sender, FileSystemEventArgs e) =>      Console.WriteLine($"File deleted: {e.FullPath}");  private void OnChanged(object sender, FileSystemEventArgs e) =>      Console.WriteLine($"File changed: {e.FullPath}");  private void OnRenamed(object sender, RenamedEventArgs e) =>      Console.WriteLine($"File renamed: {e.OldFullPath} to {e.FullPath}");  private void OnError(object sender, ErrorEventArgs e) =>      PrintException(e.GetException());  private void PrintException(Exception ex) {     if (ex != null)     {         Console.WriteLine($"Message: {ex.Message}");         Console.WriteLine("Stacktrace:");         Console.WriteLine(ex.StackTrace);         PrintException(ex.InnerException);     } } 

Будет хорошо, если вы увеличите размер внутреннего буфера с помощью свойства InternalBufferSize, если планируете отслеживать большое количество изменений. Однако максимальный размер буфера ограничен 64 КБ.


В заключение напоминаю про открытый урок, посвященный исключениям и нюансам работы с ними, который пройдет 18 июля.

На уроке обсудим, что такое исключения и то, как мы их можем отловить и обработать. Рассмотрим общие и некоторые частные случаи по работе с исключительными ситуациями в .NET. Записаться на урок можно на странице курса «C# Developer. Professional».


ссылка на оригинал статьи https://habr.com/ru/articles/826776/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *