Работа с классом FileStream в C#

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

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

Как создать FileStream (открыть файл)

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

Конструктор с двумя параметрами: путь и способ открытия файла (FileMode)

Наиболее распространенный способ создания FileStream:

public FileStream (string path, FileMode mode);

path — относительный или абсолютный путь к файлу;

mode — способ открытия файла. Здесь FileMode — это перечисление, имеющее следующие поля:

Имя Значение Описание
CreateNew 1 Создавать новый файл. Если файл уже существует, создается исключение IOException.
Create 2 Создавать новый файл. Если файл уже существует, он будет перезаписан. Если файл уже существует, но является скрытым, создается исключение UnauthorizedAccessException.
Open 3 Открыть существующий файл. Если файл не существует, то создается исключение FileNotFoundException
OpenOrCreate 4 Открыть файл, если он существует, в противном случае должен быть создан новый файл.
Truncate 5 Открыть и перезаписать существующий файл. Файл становится доступным только для записи. Попытка выполнить чтение из файла, открытого с помощью FileMode.Truncate, вызывает исключение ArgumentException.
Append 6 Открывает файл, если он существует, и находит конец файла, либо создает новый файл. Файл открывается только для записи.

Пример открытия файла с использованием этого конструктора:

string fileName = @"C:\CSharp Output\TextFile.txt";
FileStream fs = new FileStream(fileName, FileMode.CreateNew);

Конструктор с тремя параметрами: путь, способ открытия файла (FileMode) и доступ к файлу (FileAccess)

Второй вариант конструктора FileStream:

public FileStream (string path, FileMode mode, FileAccess access);

здесь FileAccess перечисление, которое определяет, каким образом объект FileStream может получить доступ к файлу (чтение, запись, чтение и запись) и содержит следующие поля:

Имя Значение Описание
Read 1 Доступ для чтения файла
ReadWrite 3 Доступ для чтения и записи файла
Write 2 Доступ для записи в файл

Пример открытия файла с использование этого конструктора:

string fileName = @"C:\CSharp Output\TextFile.txt";
FileStream fs = new FileStream(fileName, FileMode.CreateNew, FileAccess.ReadWrite);

Конструктор с четырьмя параметрами: путь, способ открытия файла (FileMode), доступ к файлу (FileAccess), способ совместного использования (FileShare)

Третий конструктор

public FileStream (string path, FileMode mode, FileAccess access, FileShare share);

здесь FileShare — перечисление, определяющее способ совместного использования файла:

Имя Значение Описание
Delete 4 Разрешает последующее удаление файла.
Inheritable 16 Разрешает наследование дескриптора файла дочерними процессами.
None 0 Отклоняет совместное использование текущего файла.
Read 1 Разрешает последующее открытие файла для чтения.
ReadWrite 3 Разрешает последующее открытие файла для чтения или записи.
Write 2 Разрешает последующее открытие файла для записи.

Обычно это перечисление используется для определения того, могут ли два процесса одновременно считывать данные из одного файла. Так, например, если файл открыт с указанием FileShare.Read, то другие пользователи могут открыть файл для чтения, но не для записи. В примере ниже показано открытие файла с запретом на его использование другими процессами:

string fileName = @"C:\CSharp Output\TextFile.txt";
FileStream fs = new FileStream(fileName, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);

Кроме перечисленных выше конструкторов FileStream, файл можно также открыть, используя методы класса FileInfo, а также использовать другие виды конструкторов FileStream, например, указывая дополнительные параметры открытия файла, используя перечисление FileOptions.

Закрытие файлового потока

FileStream реализует интерфейс IDisposable. Это означает, что по окончании использования объекта, выделенную ему память следует прямо или косвенно освободить.

Прямое освобождение памяти может быть реализовано следующим образом:

string fileName = @"C:\CSharp Output\TextFile.txt";
FileStream? fs = null; //изначально присваиваем переменной значение null
try
{
     FileStream fs = new FileStream(fileName, FileMode.CreateNew);//создаем новый файл
     //здесь работаем с файлом
}
catch (Exception ex)
{
     //обрабатываем исключение ex
     fs?.Close(); //в случае возникновения ошибки, проверяем имеет ли fs значение отличное от null и освобождаем выделенную память
}
finally
{
    fs?.Close();
} 

Косвенное освобождение памяти может быть реализовано с использованием конструкции using следующим образом:

string fileName = @"C:\CSharp Output\TextFile.txt";
using FileStream fs = new FileStream(fileName, FileMode.CreateNew);//создаем новый файл
{
     //здесь работаем с файлом
}

После выполнения всех необходимых операций с файлом память будет освобождена автоматически и нам не потребуется вызывать методы Close() или Dispose().

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

Свойства FileStream

Рассмотрим основные свойства и методы FileStream.

Свойство Значение Описание
CanRead bool Получает значение, указывающее, поддерживает ли текущий поток чтение.
CanSeek bool Получает значение, указывающее, поддерживает ли текущий поток поиск (произвольное перемещение) по файлу.
CanWrite bool Получает значение, указывающее, поддерживает ли текущий поток запись.
IsAsync bool Получает значение, указывающее, как был открыт FileStream: синхронно или асинхронно.
Length long Получает длину потока в байтах.
Name string Получает абсолютный путь к файлу, открытому в функции FileStream.
Position long Возвращает или задает текущую позицию этого потока.

Попробуем открыть какой-либо файл на диске и прочитать его свойства:

string fileName = @"C:\CSharp Output\TextFile.txt";
using FileStream fs = new FileStream(fileName, FileMode.Open);//создаем новый файл
{
     Console.WriteLine($"CanRead - {fs.CanRead}");
     Console.WriteLine($"CanSeek - {fs.CanSeek}");
     Console.WriteLine($"CanWrite - {fs.CanWrite}");
     Console.WriteLine($"IsAsync - {fs.IsAsync}");
     Console.WriteLine($"Размер файла - {fs.Length}");
     Console.WriteLine($"Абсолютный путь - {fs.Name}");
     Console.WriteLine($"Текущая позиция - {fs.Position}");
}

В результате, получим следующий вывод консоли:

CanRead — True
CanSeek — True
CanWrite — True
IsAsync — False
Размер файла — 35
Абсолютный путь — C:\CSharp Output\TextFile.txt
Текущая позиция — 0

Следует отметить, что значение IsAsync = false не означает, что мы не сможем вызывать асинхронные методы чтения/записи файла. Однако, фактически, эти операции будут выполняться синхронно, несмотря на то, что интерфейс не будет блокироваться (см. описание свойства на сайте Microsoft). Чтобы открыть файл в асинхронном режиме, можно воспользоваться следующим конструктором FileStream:

string fileName = @"C:\CSharp Output\TextFile.txt";
using FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None, 4096, FileOptions.Asynchronous);//создаем новый файл
{
     //работаем с файлом
     //в данном случае файл был открыт только для чтения (см. FileAccess)
}

В пятом параметре мы указали число 4096 — размер буфера (об этом поговорим ниже), а шестым — указали дополнительную настройку доступа к файлу, а именно — открыть файл в асинхронном режиме.

Методы FileStream

Ниже в таблице представлены основные методы работы с FileStream и их асинхронные аналоги

Метод Асинхронный аналог Описание
CopyTo() CopyToAsync() Копирует данные из текущего потока и записывает их в другой поток. Методы имеют перегруженные версии
Dispose() DisposeAsync() Освобождает неуправляемые ресурсы, используемые объектом FileStream, а при необходимости освобождает также управляемые ресурсы.
Flush() FlushAsync() Очищает буферы потока и вызывает запись всех буферизованных данных в файл.
Lock() Запрещает другим процессам чтение объекта FileStream и запись в этот объект.
Unlock() Разрешает доступ других процессов ко всему ранее заблокированному файлу или его части.
Read() ReadAsync() Выполняет чтение блока байтов из потока и запись данных в заданный буфер. Методы имеют перегруженные версии
ReadByte() Считывает байт из файла и перемещает положение чтения на один байт.
Seek(Int64, SeekOrigin) Устанавливает текущее положение этого потока на заданное значение.
SetLength() Устанавливает длину этого потока на заданное значение.
Write() WriteAsync() Записывает блок байтов в файловый поток. Методы имеют перегруженные версии
WriteByte() Запись байта в текущую позицию в потоке файла.

 

Запись и чтение файлов с использованием FileStream

FileStream позволяет нам производить чтение и запись данных на уровне байтов для чего необходимо использовать буфер — массив byte[]. Соответственно, все данные, которые мы хотим прочитать/записать с использованием FileStream должны быть представлены (в случае записи) или будут представлены (в случае чтения) в виде массива. Отсюда вытекает некоторое неудобство при работе с использованием FileStream, например, с текстовыми файлами для которых в .NET C# предусмотрены другие, более удобные классы, работу которых мы также рассмотрим. При этом, FileStream может оказаться достаточно удобным способом доступа к бинарным файлам, особенно в случае, когда нам требуется произвольный доступ и чтение определенной части файла или же при шифровании файлов.

Запись файла

Для начала рассмотрим пример записи строки в файл.

string fileName = @"C:\CSharp Output\TextFile.txt";
using FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate);//создаем новый файл
{
     string str = "Привет FileStream!\n"; //строка, которую необходимо записать
     Console.WriteLine($"Кодировка по умолчанию: {Encoding.Default.EncodingName}"); 
     byte[] buffer = Encoding.Default.GetBytes(str); //получаем из строки массив байтов, используя текущую кодировку
     fs.Write(buffer);//записываем буфер в файл
}

Для записи строки в файл мы используем кодировку по умолчанию (Unicode (UTF-8). Как было сказано выше, перед записью мы должны представить строку в виде массива байтов. Для этого мы воспользовались методом класса Encoding.GetBytes(). После того, как мы получили массив байтов, мы передаем его в синхронный метод Write для записи файла.

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

using FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate);//создаем новый файл
{
     string str = "Привет FileStream!\n"; //строка, которую необходимо записать
     byte[] buffer = Encoding.Default.GetBytes(str); //получаем из строки массив байтов, используя текущую кодировку
     fs.Write(buffer);//записываем буфер в файл
     fs.WriteByte(255);                    
     fs.WriteByte(15);
}

Теперь, если открыть файл в текстовом редакторе, то вместо строки и двух чисел мы увидим набор непонятных символов.

Чтение файла

В примере выше мы записали в файл строку и два байта в результате чего файл стал «нечитабельным». Попробуем провести обратную операцию с этим же файлом — прочитаем строку и два байта и выведем полученные значения в консоль.

using FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);//создаем новый файл
{
     string str = "Привет FileStream!\n"; //строка, которую необходимо прочитать
     int srtSize = Encoding.Default.GetByteCount(str); //чтобы правильно прочитать строку - необходимо знать размер
     byte[] buffer = new byte[srtSize]; //создаем буфер в который будем считвать строку
     fs.Read(buffer,0,srtSize); //читаем с начала файла strSize байт
     Console.WriteLine(Encoding.Default.GetString(buffer)); //преобразуем массив в строку, используя кодировку по умолчанию
     Console.WriteLine(fs.ReadByte());
     Console.WriteLine(fs.ReadByte());
}

В результате, в консоли увидим следующий текст:

Привет FileStream!

255
15

Чтение больших файлов и работа с буфером

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

using FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate);
{
     int buferSize = 10; //задаем размер буфера
     byte[] buffer = new byte[buferSize];//выделяем память под буфер
     while (true)
     {
         int readCount = fs.Read(buffer,0,buferSize);//пробуем считать buferSize байт из файла
         if (readCount==0) //ничего не прочитали - достигнут конец файла
         break;
         Console.Write(Encoding.Default.GetString(buffer));
     }
}
Произвольный доступ к данным

Чаще всего, бинарные файлы имеют какую-либо определенную структуру. Например, в файле базы данных SQLite первые 100 байт выделяются на различные мета-данные — указание версии, размера страницы и т.д. Зная структуру файла, мы можем получать произвольный доступ к частям файла и получать или записывать необходимые данные. Для произвольного доступа к определенной части файла используется метод Seek(Int64, SeekOrigin). В этом методе первый параметр — это количество байт на которые необходимо сместить курсор в потоке, а второй параметр — это перечисление, которое указывает относительно какой позиции необходимо смещаться:

Begin 0 Смещение относительно начала потока
Current 1 Смещение относительно текущего положения в потоке
End 2 Смещение с конца потока

Рассмотрим произвольный доступ к данным файла на следующем примере примере.

string fileName = @"c:\CSharp Output\TextFile.txt";
using FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate);
{
     //записываем в файл строку
     string str = "1234567890"; 
     byte[] buffer = Encoding.Default.GetBytes(str); 
     fs.Write(buffer);


     /*Произвольное чтение данных из файла*/
     fs.Seek(2, SeekOrigin.Begin);//смещаемся на 2 байт относительно начала
     buffer = new byte[1];
     fs.Read(buffer);
     Console.WriteLine(Encoding.Default.GetString(buffer));

     fs.Seek(2, SeekOrigin.Current); //смещаемся на два байта относительно текущей позиции
     fs.Read(buffer);
     Console.WriteLine(Encoding.Default.GetString(buffer));

     fs.Seek(-3, SeekOrigin.End); //смещаемся на 3 байта относительно конца файла
     fs.Read(buffer);
     Console.WriteLine(Encoding.Default.GetString(buffer));
}

В консоли мы увидим следующие числа:

3
6
8

Рассмотрим подробно, как происходило чтение файла:

1. Вначале мы смещаемся относительно начала файла на 2 байта. Курсор становится в позиции, указанной на рисунке красной линией:

seek

2. Производится чтение 1 символа в буфер — курсор смещается на одну позицию вправо:

seek

3. Смещаемся на два байта относительно текущей позиции

seek

4. Производится чтение 1 символа в буфер — курсор смещается на одну позицию вправо:

seek

5. Производится смещение на 3 байта относительно конца файла:

seek

6. Считывается один символ:

seek

В итоге мы увидели в консоли три числа — 3, 6 и 8.

Итого

Класс FileStream используется для чтения и записи файлов, оперируя при этом байтами. Поэтому объекты этого класса удобно использовать для работы с бинарными файлами, особенно, если необходим произвольный доступ к частям файла. FileStream может также использоваться для работы и с текстом, однако, при этом, строки необходимо преобразовывать в массив байтов, используя класс Encoding из пространства имен System.Text, поэтому с текстовыми файлами удобнее использовать другие, более специализированные классы.

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
guest
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии