Потоки данных и файловый ввод-вывод

Потоки обрабатывают ввод и вывод данных программы. Входной поток можно рассматривать, как последовательность байтов, входящих в программу из устройства-ис­точника, а выходной — как последовательность байтов, попадающих из программы на устройство-приемник.

В языке С# и библиотеке .NET, потоки данных представлены отдельными объекта­ми из классов, находящихся в пространстве имен System.IO.

Потоки иже знакомы нам. Так, например, знакомый читателю метод Console.ReadLine по умолчанию читает дан­ные стандартного потока (объекта одного из классов System.IO, передающего символы, набираемые на клавиатуре, в используемую программу). Аналогичным образом, метод Console.WriteLine по умолчанию записывает данные стандартного потока, при выводе символов на экран.

Классы потоков System.IOэффективно ограждают программиста от обилия низко­уровневых деталей, связанных с устройствами ПК и его операционной системой. Это не только в значительной степени облегчает выполнение операций ввода-вывода, но и предоставляет стандартизированный, не зависящий от состояния устройств, способ вы­полнения операций с потоками. Такие программы намного проще в поддержке — отпа­ла необходимость их изменять, следуя в ногу за всеми изменениями технологии более низкого, аппаратного уровня.

Виды файлов

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

Текстовые файлы состоят из битов и байтов, которые, если их правильно интер­претировать, есть ни что иное, как последовательность символов. (Некоторые файлы, например, используют кодировку Unicode.) Они предназначены для прочтения пользо­вателем, как, например, файлы, создаваемые, сохраняемые и открываемые в Notepad.

Бинарные файлы создаются и обрабатываются только компьютерными программами.

Программы, написанные на языке С#, обеспечивают доступ к бинарным файлам через бинарные потоки. По аналогии, доступ к текстовым файлам и их обработка осуществ­ляется через символьные потоки.

Бинарные файлы можно прочесть или написать более эффективно — программные данные в таком файле сохраняются в том самом виде, в котором они представлены в памяти компьютера. Отпадает необходимость в преобразованиях, отнимающих процес­сорное время. Символьные потоки менее эффективны. Так, например, все числовые данные для сохранения в текстовом файле требуют предварительного преобразования.

Классы файлового ввода-вывода

Потоковые классы расположены в пространстве имен System.IO, вместе с другими классами, которые используются для управления каталогами и файлами. Чтобы подроб­нее ознакомиться с ними, следует обратиться к документации библиотеки .NET (отно­сительно пространства имен System.IO).

Экземпляр класса FileInfo инкапсулирует файл в каталоге файловой системы компьютера. Он позволяет работать с файлом разными методами, служит для получения подробных о нем сведе­ний.

Экземпляр класса FileInfo представляет файл в файловой системе компьютера. Он позволяет получать и изменять данные о файле создавать новые и удалять ненужные файлы.

Классы StreamReader, StreamWriter - это символьные потоки ввода-вывода. Экземпляры классов StreamReader и StreamWriter представляют потоки, предназна­ченные соответственно, для чтения или записи текста (в текстовый файл).

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

Если программа включает в себя только текстовые данные, следует воспользовать­ся классами StreamReader и StreamWriter, поскольку для этой цели они более удобны.

Обратим внимание на то, что объект FileInfo не представляет собой поток. Для дос­тупа к объектам StreamReader и StreamWriter, обеспечивающим работу с файлом из объекта FileInfo, нужно использовать методы OpenText и CreateText.

По аналогии, методы OpenRead и OpenWrite объекта FileInfo можно использовать для получения объекта FileStream, который и обеспечит чтение и запись информации в ас­социированный ему бинарный или текстовый файл.

Класс FileInfo

При создании нового объекта FileInfo, необходимо связать его с основным файлом — передать конструктору строку с именем последнего. Это имя может относиться не толь­ко к уже существующему, но и к создаваемому файлу. О процессе создания файлов с помощью FileInfo мы расскажем далее.

Предположим, что файл C:\MyTestFile\myFile.txt уже существует. Следующий оператор создает объект FileInfo по имени myFileObj, который представляет собой файл myFile.txt, расположенный в каталоге C:\MyTestFile:

FileInfo myFileObj = new FileInfo(@"C:\MyTestFile\myFile.txt");

Рассмотрим некоторые члены класса FileInfo:

СоруТо копирует файл, представленный объектом Fllelnfo,в новый файл.

Delete удаляет файл, представленный объектом Fllelnfo

FullName возвращает полное (включая путь) имя файла, представленного объектом Filelnfo

Length возвращает размер (в байтах) файла, представленного объектом Filelnfo

Name возвращает краткое (без указания пути) имя файла, представленного объектом FileInfo.

Пример. Написать программу, которая при помощи описанных выше методов выводит информацию о файле и копирует один файл в другой.

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

namespace Файлы

{

class Program

{

static void Main(string[] args)

{

try

{

FileInfo myFileObj = new FileInfo(@"D:\MyTestFile\myFile.txt");

Console.WriteLine("Имя файла " + myFileObj.Name);

Console.WriteLine("Полное имя файла " + myFileObj.FullName);

Console.WriteLine("Размер файла " + myFileObj.Length);

Console.WriteLine("Дата создания файла " + myFileObj.CreationTime);

myFileObj.CopyTo(@"D:\myTestFile\myYourFile.txt", true);

}

catch (IOException exObj)

{

Console.WriteLine(exObj);

}

Console.ReadLine();

}

}

}

Строка using System.IO; добавлена, чтобы не указывать пространство имен System.IO перед Filelnfo.

Стоит отметить, что рабочий файл часто остается надолго открытым. За этот промежуток времени дру­гие программы могут получить к нему доступ и его изменить. Как результат, файл мо­жет быть случайно удален, переименован, его содержимое изменено. При попытке об­ращения к несуществующему файлу, возникнет исключение. Поэтому весьма важно умело использовать механизм обработки исключений С#.

System.IOсодержит еще один класс — File,имеющий функциональность, сходную с фун­кциональностью класса FileInfo.В отличие от FileInfo,элементы класса Fileобъявлены как static.При использовании класса Fileсоздавать новый объект (как при FileInfo) не требуется. Например, чтобы скопировать файл C:\MyTestFiles\MyFile.txt в C:\MyTestFiles\YourFile.txt,можно воспользоваться методом Сорукласса File без предварительного создания объекте:

File.Copy(@"C:\MyTestFile\myFile.txt",@"C:\myTestFile\myYourFile_.txt" );

Зачем же тогда применять FileInfo? Вызов каж­дого метода класса File требует дополнительных ресурсов процессора, так как проверке безопасного доступа к файлу производится многократно (даже если к одному и тому же файлу обращаются с помощью разных методов). Класс FileInfo производит только одну проверку — при создании объекта FileInfo. После ее завершения вся остальная обработ­ка происходит без проверок безопасности.

Текстовый ввод-вывод при помощи
Stream Reader и Stream Writer

Классы StreamReader и StreamWriter специально разработаны, чтобы работать с символьными потоками. Эти потоки применимы только для текстовых файлов. Несколь­ко методов этих классов показаны далее.

Оба класса используют метод Close, закрывающий поток. Системные ресурсы, выделенные под него, освобождаются, доступ других потоков к файлу становится возмож­ным (в каждый момент времени только один поток имеет доступ к файлу). Важно знать, что если поток не закрыт и не ассоциирован с запушенной программой, то высока ве­роятность его повреждения.

Даже если вы не закроете поток явно, использовав метод Close, система автомати­чески закроет файл при нормальном завершении программы. Если же программа закон­чит свою работу аварийно, рабочие файлы останутся открытыми. Поэтому следует зак­рывать поток сразу же после того, как потребность в нем отпадет.

Вот некоторые члены класса StreamWriter

Close закрывает выходной поток и освобождает ассоциированные с ним ресурсы.

Write выводит в поток один или несколько символов без символа новой строки.

WriteLine выводит один или несколько символов и добавляет символ новой строки.

Некоторые члены класса StreamReader

Close закрывает выходной поток и освобождает ассоциированные с ним ресурсы.

Read читает очередной символ (символы) из входного потока.

ReadLine читает очередную последовательность символов из входного потока и возвращает ее в форме строки. Возвращает null, если достигнут конец файла.

Метод WriteLine удобен для записи строки текста (в формате string) в поток StreamWriter. Метод WriteLine автоматически вставляет символ новой строки после каж­дого строкового аргумента, записываемого в файл. Например, если StreamWriter исполь­зуется для записи в файл myFile и метод WriteLine вызывается три раза, то myFile будет содержать три символа новой строки (NL), — по одному после каждой строки EOF.

Соответственно, метод ReadLine читает данные до следующего символа новой стро­ки в файле и возвращает символы из этого участка файла в форме строки string.

Метод Write класса StreamWriter является сильно перегруженным. Его можно исполь­зовать для записи в поток отдельного символа, строки, массива символов и других эле­ментов данных. Все семнадцать перегруженных методов Write описаны в документации библиотеки .NET.

Метод Write (в отличие от метода WriteLine) автоматически не добавляет в поток символ новой строки.

Элемент Read класса StreamReader соответствует двум методам. Первый имеет лишь один аргумент и считывает текущий символ из потока. Вторая его версия позволяет указать, сколько символов требуется прочесть из потока.

Следующая программа демонстрирует применение классов StreamWriter и StreamReader. Каждая строка через выходной поток StreamWriter записывается в файл C:\MyTestFiles\MyFile.txt, который затем закрывается. После этого программа открывает файл C:\MyTestFiles\MyFile.txt и через входной поток StreamReader, читает содержимое файла и выводит его на консоль.

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

class TextReaderWriter

{

public static void WriteTextToFile()

{

string textLine;

StreamWriter outstream = null;

try

{

FileInfo textFile = new FileInfo (@"C:\MyTestFile\MyFile.txt");

outstream = textFile.CreateText();

Console.WriteLine("Заполните файл тремя строками");

for (int i = 0; i < 3; i++)

{

textLine = Console.ReadLine();

outstream.WriteLine(textLine);

}

}

catch (IOException exObj)

{

Console.WriteLine(exObj);

}

finally

{

outstream.Close();

}

}

public static void ReadTextFromFile()

{

string textLine;

StreamReader inStream = null;

try

{

FileInfo textFile = new FileInfo(@"C:\MyTestFile\MyFile.txt");

inStream = textFile.OpenText();

Console.WriteLine("Чтение из файла");

textLine = inStream.ReadLine();

while (textLine != null)

{

Console.WriteLine(textLine);

textLine = inStream.ReadLine();

}

}

catch (IOException exObj)

{

Console.WriteLine(exObj);

}

finally

{

inStream.Close();

}

}

}

class Tester

{

public static void Main()

{

TextReaderWriter.WriteTextToFile();

TextReaderWriter.ReadTextFromFile();

Console.ReadLine();

}

}

Сначала создается новый экземпляр FileInfo по имени textFile. Программа передает конструктору имя файла, который нужно создать (C:\MyTestFiles\MyFile.txt). Далее вызывается метод CreateText, который возвращает экземпляр на StreamWriter для записи в новый файл C:\MyTestFiles\MyFile.txt. Этот экземпляр StreamWriter при­сваивается выходному потоку outStream. Когда файл подключается к потоку, как, на­пример, в строке:

outstream = textFile.CreateText();

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

Цикл for исполняется трижды. Во время каждой итерации, строке, введенной пользователем, присваивается textLine. В следующей строке textLine передается выходному потоку outStream как аргумент выходному потоку outStream. В конце этих трех итераций, MyStory.txt содержит символы всех трех строк, введенных пользователем.

Важно закрыть outStream, поэтому метод Close и вызывается в блоке finally, связанном с операторами catch-try. Это гарантирует, что outStream будет зак­рыт, даже если произойдет исключение.

Блок finally случайно может быть запушен для аварийно завершения во время исполнения связанного с ним блока try. Поэтому компилятор требует, чтобы все переменные из блока finally, объявленные вне блока try (outStream и inStream), инициализировались перед входом в блок try. Чтобы. избежать ошибок при компиляции, программа инициализирует outStream и inStream значением null.

Метод WriteTextToFile вызывается из метода Main. К мо­менту вызова метода ReadTextFromFile, файл MyStory.txt уже создан, и его можно использовать для демонстрации класса StreamReader. Рассмотрим ReadTextFromFile подробнее.

В программе создается экземпляр FileInfo по имени textFile. Имя файла снова переда­ется как аргумент конструктору(данный файл уже существует).

Далее метод OpenText возвращает ссылку на экземпляр StreamReader для по­лучения доступа к связанному с ним файлу MyStory.txt. Эта ссылка присваивается эк­земпляру inStream, который теперь можно использовать для чтения из MyStory.txt.

Как было сказано ранее, метод ReadLine читает текущую строку и возвращает ре­зультат в формате string. По достижении конца файла он возвращает null. Переменной textLine присваивается значение, воз­вращаемое. ReadLine. Результат проверяется в операторе цикла while, который повторяется, если textLine не равно null. Это условие нарушится, когда будет достигнут конец файла. Таким образом, если textLine не равно null, его содержимое пересылается на консоль. ReadLine повторно вызывается до тех пор, пока не будет дос­тигнут конец файла. Эта конструкция гарантирует, что каждая строка MyStory.txt, за­канчивающаяся символом новой строки, отправляется на консоль.

Наши рекомендации