Привет, Хабр!
Работа с файлами — неотъемлемая часть многих проектов. Простая запись логов, чтение конфигурационных файлов или обработка больших данных — все это является частью взаимодействовия с файловой системой.
В этой статье, мы рассмотрим основы работы с файлами и потоками в 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/
Добавить комментарий