Потоки данных и файловый ввод-вывод
Потоки обрабатывают ввод и вывод данных программы. Входной поток можно рассматривать, как последовательность байтов, входящих в программу из устройства-источника, а выходной — как последовательность байтов, попадающих из программы на устройство-приемник.
В языке С# и библиотеке .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, заканчивающаяся символом новой строки, отправляется на консоль.