Содержание
- Как создать FileStream (открыть файл)
- Конструктор с двумя параметрами: путь и способ открытия файла (FileMode)
- Конструктор с тремя параметрами: путь, способ открытия файла (FileMode) и доступ к файлу (FileAccess)
- Конструктор с четырьмя параметрами: путь, способ открытия файла (FileMode), доступ к файлу (FileAccess), способ совместного использования (FileShare)
- Закрытие файлового потока
- Свойства и методы FileStream
- Итого
Класс 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);
Третий конструктор
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
.
Свойство | Значение | Описание |
---|---|---|
Can |
bool |
Получает значение, указывающее, поддерживает ли текущий поток чтение. |
Can |
bool |
Получает значение, указывающее, поддерживает ли текущий поток поиск (произвольное перемещение) по файлу. |
CanWrite |
bool |
Получает значение, указывающее, поддерживает ли текущий поток запись. |
Is |
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}"); }
В результате, получим следующий вывод консоли:
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
и их асинхронные аналоги
Метод | Асинхронный аналог | Описание |
Copy |
Copy |
Копирует данные из текущего потока и записывает их в другой поток. Методы имеют перегруженные версии |
Dispose() |
Dispose |
Освобождает неуправляемые ресурсы, используемые объектом FileStream , а при необходимости освобождает также управляемые ресурсы. |
Flush() |
Flush |
Очищает буферы потока и вызывает запись всех буферизованных данных в файл. |
Lock() |
Запрещает другим процессам чтение объекта FileStream и запись в этот объект. |
|
Unlock() |
Разрешает доступ других процессов ко всему ранее заблокированному файлу или его части. | |
Read() |
Read |
Выполняет чтение блока байтов из потока и запись данных в заданный буфер. Методы имеют перегруженные версии |
Read |
Считывает байт из файла и перемещает положение чтения на один байт. | |
Seek(Int64, Seek |
Устанавливает текущее положение этого потока на заданное значение. | |
Set |
Устанавливает длину этого потока на заданное значение. | |
Write() |
Write |
Записывает блок байтов в файловый поток. Методы имеют перегруженные версии |
Write |
Запись байта в текущую позицию в потоке файла. |
Запись и чтение файлов с использованием 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()); }
В результате, в консоли увидим следующий текст:
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, Seek
. В этом методе первый параметр — это количество байт на которые необходимо сместить курсор в потоке, а второй параметр — это перечисление, которое указывает относительно какой позиции необходимо смещаться:
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)); }
В консоли мы увидим следующие числа:
6
8
Рассмотрим подробно, как происходило чтение файла:
1. Вначале мы смещаемся относительно начала файла на 2 байта. Курсор становится в позиции, указанной на рисунке красной линией:
2. Производится чтение 1 символа в буфер — курсор смещается на одну позицию вправо:
3. Смещаемся на два байта относительно текущей позиции
4. Производится чтение 1 символа в буфер — курсор смещается на одну позицию вправо:
5. Производится смещение на 3 байта относительно конца файла:
6. Считывается один символ:
В итоге мы увидели в консоли три числа — 3, 6 и 8.
Итого
Класс FileStream
используется для чтения и записи файлов, оперируя при этом байтами. Поэтому объекты этого класса удобно использовать для работы с бинарными файлами, особенно, если необходим произвольный доступ к частям файла. FileStream
может также использоваться для работы и с текстом, однако, при этом, строки необходимо преобразовывать в массив байтов, используя класс Encoding
из пространства имен System.Text, поэтому с текстовыми файлами удобнее использовать другие, более специализированные классы.