Чтение и запись файла. Класс FileStream
Класс FileStream представляет возможности по считыванию из файла и записи в файл. Он позволяет работать как с текстовыми файлами, так и с бинарными.
Рассмотрим наиболее важные его свойства и методы:
- Свойство Length: возвращает длину потока в байтах
- Свойство Position: возвращает текущую позицию в потоке
- Метод Read: считывает данные из файла в массив байтов. Принимает три параметра: int Read(byte[] array, int offset, int count) и возвращает количество успешно считанных байтов. Здесь используются следующие параметры:
- array - массив байтов, куда будут помещены считываемые из файла данные
- offset представляет смещение в байтах в массиве array, в который считанные байты будут помещены
- count - максимальное число байтов, предназначенных для чтения. Если в файле находится меньшее количество байтов, то все они будут считаны.
- Метод long Seek(long offset, SeekOrigin origin): устанавливает позицию в потоке со смещением на количество байт, указанных в параметре offset.
- Метод Write: записывает в файл данные из массива байтов. Принимает три параметра: Write(byte[] array, int offset, int count)
- array - массив байтов, откуда данные будут записываться в файла
- offset - смещение в байтах в массиве array, откуда начинается запись байтов в поток
- count - максимальное число байтов, предназначенных для записи
FileStream представляет доступ к файлам на уровне байтов, поэтому, например, если вам надо считать или записать одну или несколько строк в текстовый файл, то массив байтов надо преобразовать в строки, используя специальные методы. Поэтому для работы с текстовыми файлами применяются другие классы.
|
|
В то же время при работе с различными бинарными файлами, имеющими определенную структуру FileStream может быть очень даже полезен для извлечения определенных порций информации и ее обработки.
Посмотрим на примере считывания-записи в текстовый файл:
Console.WriteLine("Введите строку для записи в файл:");
string text = Console.ReadLine();
// запись в файл
using (FileStream fstream = new FileStream(@"C:\SomeDir\noname\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(@"C:\SomeDir\noname\note.txt"))
{
// преобразуем строку в байты
byte[] array = new byte[fstream.Length];
// считываем данные
fstream.Read(array, 0, array.Length);
// декодируем байты в строку
string textFromFile = System.Text.Encoding.Default.GetString(array);
Console.WriteLine("Текст из файла: {0}", textFromFile);
}
Console.ReadLine();
Разберем этот пример. И при чтении, и при записи используется оператор using. Не надо путать данный оператор с директивой using, которая подключает пространства имен в начале файла кода. Оператор using позволяет создавать объект в блоке кода, по завершению которого вызывается метод Dispose у этого объекта, и, таким образом, объект уничтожается. В данном случае в качестве такого объекта служит переменная fstream.
|
|
Объект fstream создается двумя разными способами: через конструктор и через один из статических методов класса File.
Здесь в конструктор передается два параметра: путь к файлу и перечисление FileMode. Данное перечисление указывает на режим доступа к файлу и может принимать следующие значения:
- Append: если файл существует, то текст добавляется в конец файл. Если файла нет, то он создается. Файл открывается только для записи.
- Create: создается новый файл. Если такой файл уже существует, то он перезаписывается
- CreateNew: создается новый файл. Если такой файл уже существует, то он приложение выбрасывает ошибку
- Open: открывает файл. Если файл не существует, выбрасывается исключение
- OpenOrCreate: если файл существует, он открывается, если нет - создается новый
- Truncate: если файл существует, то он перезаписывается. Файл открывается только для записи.
Статический метод OpenRead класса File открывает файл для чтения и возвращает объект FileStream.
Конструктор класса FileStream также имеет ряд перегруженных версий, позволяющий более точно настроить создаваемый объект. Все эти версии можно посмотреть на msdn.
И при записи, и при чтении применяется объект кодировки Encoding.Default из пространства имен System.Text. В данном случае мы используем два его метода: GetBytes для получения массива байтов из строки и GetString для получения строки из массива байтов.
В итоге введенная нами строка записывается в файл note.txt. По сути это бинарный файл (не текстовый), хотя если мы в него запишем только строку, то сможем посмотреть в удобочитаемом виде этот файл, открыв его в текстовом редакторе. Однако если мы в него запишем случайные байты, например:
fstream.WriteByte(13);
fstream.WriteByte(103);
То у нас могут возникнуть проблемы с его пониманием. Поэтому для работы непосредственно с текстовыми файлами предназначены отдельные классы - StreamReader и StreamWriter.
Произвольный доступ к файлам
Нередко бинарные файлы представляют определенную стрктуру. И, зная эту структуру, мы можем взять из файла нужную порцию информации или наоброт записать в определенном месте файла определенный набор байтов. Например, в wav-файлах непосредственно звуковые данные начинаются с 44 байта, а до 44 байта идут различные метаданные - количество каналов аудио, частота дискретизации и т.д.
С помощью метода Seek() мы можем управлять положением курсора потока, начиная с которого производится считывание или запись в файл. Этот метод принимает два параметра: offset (смещение) и позиция в файле. Позиция в файле описывается тремя значениями:
- SeekOrigin.Begin: начало файла
- SeekOrigin.End: конец файла
- SeekOrigin.Current: текущая позиция в файле
Курсор потока, с которого начинается чтение или запись, смещается вперед на значение offset относительно позиции, указанной в качестве второго параметра. Смещение может отрицательным, тогда курсор сдвигается назад, если положительное - то вперед.
Рассмотрим на примере:
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("Текст из файла: {0}", 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("Текст из файла: {0}", textFromFile); // hello house
}
Console.Read();
}
}
Вызов fstream.Seek(-5, SeekOrigin.End) перемещает курсор потока в конец файлов назад на пять символов:
То есть после записи в новый файл строки "hello world" курсор будет стоять на позиции символа "w".
После этого считываем четыре байта начиная с символа "w". В данной кодировке 1 символ будет представлять 1 байт. Поэтому чтение 4 байтов будет эквивалентно чтению четырех сиволов: "worl".
Затем опять же перемещаемся в конец файла, не доходя до конца пять символов (то есть опять же с позиции символа "w"), и осуществляем запись строки "house". Таким образом, строка "house" заменяет строку "world".
Закрытие потока
В примерах выше дл закрытия потока применяется конструкция using. После того как все операторы и выражения в блоке using отработают, объект FileStream уничтожается. Однако мы можем выбрать и другой способ:
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()