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

Чтение и запись файла. Класс 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()

 

 


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: