**

[Конспект "Работа с потоками (stream) и файловой системой"](https://github.com/kolei/OAP/blob/master/articles/t5_files.md)

** **

Работа с дисками

** Класс **DriveInfo** имеет статический метод GetDrives, который возвращает имена всех логических дисков компьютера. Также он предоставляет ряд полезных свойств: * **AvailableFreeSpace:** указывает на объем доступного свободного места на диске в байтах * **DriveFormat:** получает имя файловой системы * **DriveType:** представляет тип диска * **IsReady:** готов ли диск (например, DVD-диск может быть не вставлен в дисковод) * **Name:** получает имя диска * **TotalFreeSpace:** получает общий объем свободного места на диске в байтах * **TotalSize:** общий размер диска в байтах * **VolumeLabel:** получает или устанавливает метку тома **

Получим имена и свойства всех дисков на компьютере:

** ```js using System; using System.IO; DriveInfo[] drives = DriveInfo.GetDrives(); foreach (DriveInfo drive in drives) { Console.WriteLine($"Название: {drive.Name}"); Console.WriteLine($"Тип: {drive.DriveType}"); if (drive.IsReady) { Console.WriteLine($"Объем диска: {drive.TotalSize}"); Console.WriteLine($"Свободное пространство: {drive.TotalFreeSpace}"); Console.WriteLine($"Метка: {drive.VolumeLabel}"); } Console.WriteLine(); } ``` **

Вывод:

** ```js Название: C:\ Тип: Fixed Объем диска: 257128853504 Свободное пространство: 50015592448 Метка: Название: D:\ Тип: Fixed Объем диска: 644245090304 Свободное пространство: 211880443904 Метка: Новый том Название: E:\ Тип: Fixed Объем диска: 1356135919616 Свободное пространство: 1355963248640 Метка: Новый том Название: F:\ Тип: Fixed Объем диска: 742383415296 Свободное пространство: 650652270592 Метка: ssd ``` **

Работа с каталогами

** Класс Directory предоставляет ряд статических методов для управления каталогами. Некоторые из этих методов: * **CreateDirectory(path):** создает каталог по указанному пути path * **Delete(path):** удаляет каталог по указанному пути path * **Exists(path):** определяет, существует ли каталог по указанному пути path. Если существует, возвращается true, если не существует, то false * **GetDirectories(path):** получает список каталогов в каталоге path * **GetFiles(path):** получает список файлов в каталоге path * **Move(sourceDirName, destDirName):** перемещает каталог * **GetParent(path):** получение родительского каталога **

Класс DirectoryInfo

** Данный класс предоставляет функциональность для создания, удаления, перемещения и других операций с каталогами. Во многом он похож на Directory. Некоторые из его свойств и методов: * **Create():** создает каталог * **CreateSubdirectory(path):** создает подкаталог по указанному пути path * **Delete():** удаляет каталог * **Свойство Exists:** определяет, существует ли каталог * **GetDirectories():** получает список каталогов * **GetFiles():** получает список файлов * **MoveTo(destDirName):** перемещает каталог * **Свойство Parent:** получение родительского каталога * **Свойство Root:** получение корневого каталога **

Получение списка файлов и подкаталогов

** ```js string dirName = "C:\\"; if (Directory.Exists(dirName)) { Console.WriteLine("Подкаталоги:"); string[] dirs = Directory.GetDirectories(dirName); foreach (string s in dirs) { Console.WriteLine(s); } Console.WriteLine(); Console.WriteLine("Файлы:"); string[] files = Directory.GetFiles(dirName); foreach (string s in files) { Console.WriteLine(s); } } ``` **

Вывод:

** ```js Подкаталоги: C:\$Recycle.Bin C:\$WinREAgent C:\AMD C:\Documents and Settings C:\MSI C:\OneDriveTemp C:\PerfLogs C:\Program Files C:\Program Files (x86) C:\ProgramData C:\Recovery C:\System Volume Information C:\tenorshare C:\TwitchLink C:\Users C:\Windows Файлы: C:\appverifUI.dll C:\DumpStack.log C:\DumpStack.log.tmp C:\hiberfil.sys C:\pagefile.sys C:\swapfile.sys C:\vfcompat.dll ``` Обратите внимание на использование слешей в именах файлов. Либо мы используем двойной слеш: C:\\, либо одинарный, но тогда перед строкой ставим знак `@: @"C:\Program Files"` `Можно вместо обратных слешей использовать прямые. Windows нормально их воспринимает` **

Создание каталога

** ```js string path = @"C:\SomeDir"; string subpath = @"program\avalon"; DirectoryInfo dirInfo = new DirectoryInfo(path); if (!dirInfo.Exists) { dirInfo.Create(); } dirInfo.CreateSubdirectory(subpath); ``` Вначале проверяем, а нет ли такой директории, так как если она существует, то ее создать будет нельзя, и приложение выбросит ошибку. В итоге у нас получится следующий путь: `C:\SomeDir\program\avalon` **

Получение информации о каталоге

** ```js string dirName = "C:\\Program Files"; DirectoryInfo dirInfo = new DirectoryInfo(dirName); Console.WriteLine($"Название каталога: {dirInfo.Name}"); Console.WriteLine($"Полное название каталога: {dirInfo.FullName}"); Console.WriteLine($"Время создания каталога: {dirInfo.CreationTime}"); Console.WriteLine($"Корневой каталог: {dirInfo.Root}"); ``` **

Удаление каталога

** Нам нужно передать в метод `Delete` дополнительный параметр булевого типа, который укажет, что папку надо удалять со всем содержимым (иначе не получится): ```js string dirName = @"C:\SomeFolder"; try { DirectoryInfo dirInfo = new DirectoryInfo(dirName); dirInfo.Delete(true); Console.WriteLine("Каталог удален"); } catch (Exception ex) { Console.WriteLine(ex.Message); } ``` **

Либо так:

** ```js string dirName = @"C:\SomeFolder"; Directory.Delete(dirName, true); ``` **

Перемещение каталога

** ```js string oldPath = @"C:\SomeFolder"; string newPath = @"C:\SomeDir"; DirectoryInfo dirInfo = new DirectoryInfo(oldPath); if (dirInfo.Exists && Directory.Exists(newPath) == false) { dirInfo.MoveTo(newPath); } ``` `При перемещении надо учитывать, что новый каталог, в который мы хотим перемесить все содержимое старого каталога, не должен существовать.` **

Работа с файлами. Классы File и FileInfo

** Подобно паре Directory/DirectoryInfo для работы с файлами предназначена пара классов File и FileInfo. С их помощью мы можем создавать, удалять, перемещать файлы, получать их свойства и многое другое. Некоторые полезные методы и свойства класса `FileInfo`: * **`CopyTo(path)`** копирует файл в новое место по указанному пути path * **`Create()`:** создает файл * **`Delete()`:** удаляет файл * **`MoveTo(destFileName)`:** перемещает файл в новое место * **Свойство `Directory`:** получает родительский каталог в виде объекта DirectoryInfo * **Свойство `DirectoryName`:** получает полный путь к родительскому каталогу * **Свойство `Exists`:** указывает, существует ли файл * **Свойство `Length`:** получает размер файла * **Свойство `Extension`:** получает расширение файла * **Свойство `Name`:** получает имя файла * **Свойство `FullName`:** получает полное имя файла Класс File реализует похожую функциональность с помощью статических методов: * **`Copy()`:** копирует файл в новое место * **`Create()`:** создает файл * **`Delete()`:** удаляет файл * **`Move`:** перемещает файл в новое место * **`Exists(file)`:** определяет, существует ли файл **

Получение информации о файле

** ```js string path = @"C:\apache\hta.txt"; FileInfo fileInf = new FileInfo(path); if (fileInf.Exists) { Console.WriteLine("Имя файла: {0}", fileInf.Name); Console.WriteLine("Время создания: {0}", fileInf.CreationTime); Console.WriteLine("Размер: {0}", fileInf.Length); } ``` **

Удаление файла

** ```js string path = @"C:\apache\hta.txt"; FileInfo fileInf = new FileInfo(path); if (fileInf.Exists) { fileInf.Delete(); // альтернатива с помощью класса File // File.Delete(path); } ``` **

Перемещение файла

** ```js string path = @"C:\apache\hta.txt"; string newPath = @"C:\SomeDir\hta.txt"; FileInfo fileInf = new FileInfo(path); if (fileInf.Exists) { fileInf.MoveTo(newPath); // альтернатива с помощью класса File // File.Move(path, newPath); } ``` **

Копирование файла

** ```js string path = @"C:\apache\hta.txt"; string newPath = @"C:\SomeDir\hta.txt"; FileInfo fileInf = new FileInfo(path); if (fileInf.Exists) { fileInf.CopyTo(newPath, true); // альтернатива с помощью класса File // File.Copy(path, newPath, true); } ``` **

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

** Класс FileStream представляет возможности по считыванию из файла и записи в файл. Он позволяет работать как с текстовыми файлами, так и с бинарными. **

Создание FileStream

** ```js FileStream(string filename, FileMode mode) ``` Здесь в конструктор передается два параметра: путь к файлу и перечисление (enum) FileMode. Данное перечисление указывает на режим доступа к файлу и может принимать следующие значения: * `Append`: если файл существует, то текст добавляется в конец файл. Если файла нет, то он создается. Файл открывается только для записи. * `Create`: создается новый файл. Если такой файл уже существует, то он перезаписывается * `CreateNew`: создается новый файл. Если такой файл уже существует, то он приложение выбрасывает ошибку * `Open`: открывает файл. Если файл не существует, выбрасывается исключение * `OpenOrCreate`: если файл существует, он открывается, если нет - создается новый * `Truncate`: если файл существует, то он перезаписывается. Файл открывается только для записи. `Другой способ создания объекта FileStream представляют статические методы класса File:` ```js FileStream File.Open(string file, FileMode mode); FileStream File.OpenRead(string file); FileStream File.OpenWrite(string file); ``` **

Свойства и методы FileStream

** * Свойство `Length`: возвращает длину потока в байтах * Свойство `Position`: возвращает текущую позицию в потоке * `void CopyTo(Stream destination)`: копирует данные из текущего потока в поток destination * `Task CopyToAsync(Stream destination)`: асинхронная версия метода CopyToAsync * `int Read(byte[] array, int offset, int count)`: считывает данные из файла в массив байтов и возвращает количество успешно считанных байтов. Принимает три параметра: * `array` - массив байтов, куда будут помещены считываемые из файла данные * `offset` - представляет смещение в байтах в массиве array, в который считанные байты будут помещены * `count` - максимальное число байтов, предназначенных для чтения. Если в файле находится меньшее количество байтов, то все они будут считаны. * `Task ReadAsync(byte[] array, int offset, int count)`: асинхронная версия метода Read * `long Seek(long offset, SeekOrigin origin)`: устанавливает позицию в потоке со смещением на количество байт, указанных в параметре offset. * `void Write(byte[] array, int offset, int count)`: записывает в файл данные из массива байтов. Принимает три параметра: * `offset` - смещение в байтах в массиве array, откуда начинается запись байтов в поток * `count` - максимальное число байтов, предназначенных для записи * `ValueTask WriteAsync(byte[] array, int offset, int count)`: асинхронная версия метода Write **

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

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

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

** ```js using System; using System.IO; namespace HelloApp { class Program { static void Main(string[] args) { // создаем каталог для файла string path = @"C:\SomeDir2"; DirectoryInfo dirInfo = new DirectoryInfo(path); if (!dirInfo.Exists) { dirInfo.Create(); } Console.WriteLine("Введите строку для записи в файл:"); string text = Console.ReadLine(); // запись в файл using (FileStream fstream = new FileStream($"{path}\note.txt", FileMode.OpenOrCreate)) { // преобразуем строку в байты byte[] array = System.Text.Encoding.Default.GetBytes(text); // запись массива байтов в файл fstream.Write(array, 0, array.Length); Console.WriteLine("Текст записан в файл"); } // чтение из файла using (FileStream fstream = File.OpenRead($"{path}\note.txt")) { // преобразуем строку в байты byte[] array = new byte[fstream.Length]; // считываем данные fstream.Read(array, 0, array.Length); // декодируем байты в строку string textFromFile = System.Text.Encoding.Default.GetString(array); Console.WriteLine($"Текст из файла: {textFromFile}"); } Console.ReadLine(); } } } ``` **

Изменим выше приведенную программу, применив асинхронные методы:

** ```js using System; using System.IO; using System.Threading.Tasks; namespace HelloApp { class Program { static async Task Main(string[] args) { // создаем каталог для файла string path = @"C:\SomeDir3"; DirectoryInfo dirInfo = new DirectoryInfo(path); if (!dirInfo.Exists) { dirInfo.Create(); } Console.WriteLine("Введите строку для записи в файл:"); string text = Console.ReadLine(); // запись в файл using (FileStream fstream = new FileStream($"{path}\note.txt", FileMode.OpenOrCreate)) { byte[] array = System.Text.Encoding.Default.GetBytes(text); // асинхронная запись массива байтов в файл await fstream.WriteAsync(array, 0, array.Length); Console.WriteLine("Текст записан в файл"); } // чтение из файла using (FileStream fstream = File.OpenRead($"{path}\note.txt")) { byte[] array = new byte[fstream.Length]; // асинхронное чтение файла await fstream.ReadAsync(array, 0, array.Length); string textFromFile = System.Text.Encoding.Default.GetString(array); Console.WriteLine($"Текст из файла: {textFromFile}"); } Console.ReadLine(); } } } ``` **

Произвольный доступ к файлам

** С помощью метода `Seek` мы можем управлять положением курсора потока, начиная с которого производится считывание или запись в файл. Этот метод принимает два параметра: offset (смещение) и позиция в файле. Позиция в файле описывается тремя значениями: * `SeekOrigin.Begin`: начало файла * `SeekOrigin.End`: конец файла * `SeekOrigin.Current`: текущая позиция в файле Курсор потока, с которого начинается чтение или запись, смещается вперед на значение offset относительно позиции, указанной в качестве второго параметра. Смещение может быть отрицательным, тогда курсор сдвигается назад, если положительное - то вперед. **

Рассмотрим на примере:

** ```js using System.IO; using System.Text; class Program { static void Main(string[] args) { string text = "hello world"; // запись в файл using (FileStream fstream = new FileStream(@"D:\note.dat", FileMode.OpenOrCreate)) { // преобразуем строку в байты byte[] input = Encoding.Default.GetBytes(text); // запись массива байтов в файл fstream.Write(input, 0, input.Length); Console.WriteLine("Текст записан в файл"); // перемещаем указатель в конец файла, до конца файла- пять байт fstream.Seek(-5, SeekOrigin.End); // минус 5 символов с конца потока // считываем четыре символов с текущей позиции byte[] output = new byte[4]; fstream.Read(output, 0, output.Length); // декодируем байты в строку string textFromFile = Encoding.Default.GetString(output); Console.WriteLine($"Текст из файла: {textFromFile}"); // worl // заменим в файле слово world на слово house string replaceText = "house"; fstream.Seek(-5, SeekOrigin.End); // минус 5 символов с конца потока input = Encoding.Default.GetBytes(replaceText); fstream.Write(input, 0, input.Length); // считываем весь файл // возвращаем указатель в начало файла fstream.Seek(0, SeekOrigin.Begin); output = new byte[fstream.Length]; fstream.Read(output, 0, output.Length); // декодируем байты в строку textFromFile = Encoding.Default.GetString(output); Console.WriteLine($"Текст из файла: {textFromFile}"); // hello house } Console.Read(); } } ``` **

Вывод:

** ```js Текст записан в файл Текст из файла: worl Текст из файла: hello house ``` **

Закрытие потока

** В примерах выше для закрытия потока применяется конструкция using. После того как все операторы и выражения в блоке using отработают, объект FileStream уничтожается. Однако мы можем выбрать и другой способ: ```js FileStream fstream = null; try { fstream = new FileStream(@"D:\note3.dat", FileMode.OpenOrCreate); // операции с потоком } catch(Exception ex) { } finally { if (fstream != null) fstream.Close(); } ``` Если мы не используем конструкцию `using`, то нам надо явным образом вызвать метод `Close: fstream.Close()` **

Чтение и запись текстовых файлов. StreamReader и StreamWriter

** Класс `FileStream` не очень удобно применять для работы с текстовыми файлами. Для этого в пространстве `System.IO` определены специальные классы: `StreamReader` и `StreamWriter`. **

Запись в файл и StreamWriter

** Для записи в текстовый файл используется класс StreamWriter. Некоторые из его конструкторов, которые могут применяться для создания объекта StreamWriter: * `StreamWriter(string path)`: через параметр path передается путь к файлу, который будет связан с потоком * `StreamWriter(string path, bool append)`: параметр append указывает, надо ли добавлять в конец файла данные или же перезаписывать файл. Если равно true, то новые данные добавляются в конец файла. Если равно false, то файл перезаписываетсяя заново * `StreamWriter(string path, bool append, System.Text.Encoding encoding)`: параметр encoding указывает на кодировку, которая будет применяться при записи Свою функциональность **StreamWriter** реализует через следующие методы: * `int Close()`: закрывает записываемый файл и освобождает все ресурсы * `void Flush()`: записывает в файл оставшиеся в буфере данные и очищает буфер. * `Task FlushAsync()`: асинхронная версия метода Flush * `void Write(string value)`: записывает в файл данные простейших типов, как int, double, char, string и т.д. Соответственно имеет ряд перегруженных версий для записи данных элементарных типов, например, Write(char value), Write(int value), Write(double value) и т.д. * `Task WriteAsync(string value)`: асинхронная версия метода Write * `void WriteLine(string value)`: также записывает данные, только после записи добавляет в файл символ окончания строки * `Task WriteLineAsync(string value)`: асинхронная версия метода WriteLine ```js using System; using System.IO; namespace HelloApp { class Program { static void Main(string[] args) { string writePath = @"C:\SomeDir\hta.txt"; string text = "Привет мир!\nПока мир..."; try { using (StreamWriter sw = new StreamWriter( writePath, false, System.Text.Encoding.Default)) { sw.WriteLine(text); } using (StreamWriter sw = new StreamWriter( writePath, true, System.Text.Encoding.Default)) { sw.WriteLine("Дозапись"); sw.Write(4.5); } Console.WriteLine("Запись выполнена"); } catch (Exception e) { Console.WriteLine(e.Message); } } } } ``` **

Вывод:

** ```js Привет мир! Пока мир... Дозапись 4,5 ``` **

Используем асинхронные версии методов:

** ```js using System; using System.IO; using System.Threading.Tasks; namespace HelloApp { class Program { static async Task Main(string[] args) { string writePath = @"C:\SomeDir\hta2.txt"; string text = "Привет мир!\nПока мир..."; try { using (StreamWriter sw = new StreamWriter( writePath, false, System.Text.Encoding.Default)) { await sw.WriteLineAsync(text); } using (StreamWriter sw = new StreamWriter( writePath, true, System.Text.Encoding.Default)) { await sw.WriteLineAsync("Дозапись"); await sw.WriteAsync("4,5"); } Console.WriteLine("Запись выполнена"); } catch (Exception e) { Console.WriteLine(e.Message); } } } } ``` Обратите внимание, что асинхронные версии есть не для всех перегрузок метода Write. **

Чтение из файла и StreamReader

** Класс **StreamReader** позволяет нам легко считывать весь текст или отдельные строки из текстового файла. Некоторые из конструкторов класса **StreamReader**: * `StreamReader(string path)`: через параметр path передается путь к считываемому файлу * `StreamReader(string path, System.Text.Encoding encoding)`: параметр encoding задает кодировку для чтения файла Среди методов StreamReader можно выделить следующие: * `void Close()`: закрывает считываемый файл и освобождает все ресурсы * `int Peek()`: возвращает следующий доступный символ, если символов больше нет, то возвращает -1 * `int Read()`: считывает и возвращает следующий символ в численном представлении. Имеет перегруженную версию: Read(char[] array, int index, int count), где array - массив, куда считываются символы, index - индекс в массиве array, начиная с которого записываются считываемые символы, и count - максимальное количество считываемых символов * `Task ReadAsync()`: асинхронная версия метода Rea * `string ReadLine()`: считывает одну строку в файле * `string ReadLineAsync()`: асинхронная версия метода ReadLine * `string ReadToEnd()`: считывает весь текст из файла * `string ReadToEndAsync()`: асинхронная версия метода ReadToEnd ```js using System; using System.IO; using System.Threading.Tasks; string path = @"C:\SomeDir\hta.txt"; try { using (StreamReader sr = new StreamReader(path)) { Console.WriteLine(sr.ReadToEnd()); } // асинхронное чтение using (StreamReader sr = new StreamReader(path)) { Console.WriteLine(await sr.ReadToEndAsync()); } } catch (Exception e) { Console.WriteLine(e.Message); } ``` **

Считаем текст из файла построчно:

** ```js string path= @"C:\SomeDir\hta.txt"; using (StreamReader sr = new StreamReader(path, System.Text.Encoding.Default)) { string line; while ((line = sr.ReadLine()) != null) { Console.WriteLine(line); } } // асинхронное чтение using (StreamReader sr = new StreamReader(path, System.Text.Encoding.Default)) { string line; while ((line = await sr.ReadLineAsync()) != null) { Console.WriteLine(line); } } ```