- •1 Файлы и потоки
- •2 Потоки данных и классы
- •2.1 Стандартные потоки
- •2.2 Базовые классы для работы с файлами и потоками
- •2.2.1 Основные классы ввода и вывода
- •2.2.2 Классы на базе FileSystemInfo
- •2.2.3 Классы для работы с потоками
- •2.2.4 Классы для работы с потоками текстовых символов
- •2.3 Перечисления
- •3 Работа со стандартными потоками
- •3.1 Стандартный поток ввода
- •3.2 Стандартный поток вывода
- •3.2.1 Стандартный поток вывода сообщений об ошибках
- •3.3 Программа StdStreams
- •4 Создание потоков, связанных с файлами
- •4.1 Открытие потока FileStream
- •4.2 Открытие потоков BinaryWriter и BinaryReader
- •4.3 Закрытие потоков
- •4.4 Запись двоичных данных
- •4.5 Чтение двоичных данных
- •4.6 Программа Binary
- •4.7 Работа с текстовыми файлами
- •5 Выбор кодировки символов
- •5.1 Кодовые страницы
- •5.2 Недостатки модели кодовых страниц
- •5.3 Стандарт UNICODE
- •5.3.1 Unicode в Microsoft Windows NT/2000/XP
- •5.3.2 UNICODE в Microsoft Windows 95
- •5.4 Кодировка текстовых потоков
- •5.5 Кодировка текстовых строк в двоичных потоках
- •6 Буферизация потоков
- •6.1 Буферизация двоичных потоков
- •6.2 Буферизация текстовых потоков
- •6.3 Принудительный сброс буферов
- •7 Потоки в оперативной памяти
- •7.1 Создание потока
- •7.2 Чтение данных
- •7.3 Запись данных
- •7.4 Доступ к буферу потока MemoryStream
- •7.5 Потоки на базе строк string
- •7.6 Потоки класса StringBuilder
- •8 Краткие выводы
- •8.1 Создание и чтение бинарного файла
- •8.2 Создание текстового файла
- •8.3 Добавление в текстовый файл
- •8.4 Чтение и запись в файл строк на русском языке
- •8.5 Посимвольное чтение текстового файла
- •8.6 Построчное чтение текстового файла
- •8.7 Чтение файла полностью
4 Создание потоков, связанных с файлами
Если вам нужно создать входной или выходной поток, связанный с локальным файлом, содержащим двоичные данные, следует воспользоваться классами BinaryWriter и BinaryReader из библиотеки Microsoft .NET Framework. Что же касается чтения из файла или записи в файл текстовых данных, то для решения этой задачи обычно используются классы StreamReader и StreamWriter.
В любом случае, перед тем как читать данные из файла или записывать их в файл, необходимо выполнить операцию открытия потока, связанного с файлом. Когда работа с файлом окончена, соответствующий поток необходимо закрыть явным образом.
Обращаем ваше внимание на то, что система сборки мусора, встроенная в среду исполнения Microsoft .NET Framework, следит за использованием только объектов, находящихся в оперативной памяти, таких, как переменные, массивы, экземпляры объектов, созданных на базе классов и т. п. Что же касается потоков, связанных с файлами, то вы должны сами следить за их открытием и закрытием.
4.1 Открытие потока FileStream
Для работы с двоичными файлами ваша программа должна вначале создать поток класса FileStream, воспользовавшись соответствующим конструктором, например:
FileStream fs = new FileStream("myfile.dat", FileMode.CreateNew);
В качестве первого параметра конструктору необходимо передать полный путь к файлу или имя файла, а в качестве второго — режим открытия потока (табл. 1).
Режим
Append
Create
CreateNew
Open
OpenOrCreate
Truncate
Таблица 1: Режимы открытия файла FileMode
Описание
Если файл существует, он открывается. Текущая позиция устанавливается на конец файла. Если указанного файла нет, то он создается. Этот режим можно использовать только совместно с режимом доступа FileAccess .Write. При попытке чтения из файла, открытого подобным образом, возникает исключение ArgumentException
ОС должна создать новый файл. Если указанный файл уже существует, он будет перезаписан
ОС должна создать новый файл. Если указанный файл уже существует, возникнет исключение IOException
Требуется открыть существующий файл. Необходим доступ FilelOPermissionAccess . Read. Если требуемый файл не найден, возникает исключение System. 10. FileNotFoundException
Если указанный файл существует, он должен быть открыт. В противном случае ОС должна создать и открыть указанный файл
Требуется открыть существующий файл. После открытия файл обрезается до пулевой длины, при этом все ранее хранившиеся в нем данные пропадают.
При попытке чтения из файла, открытого подобным образом, возникает исключение
Взависимости от того, для какой цели создастся файл, вы можете выбрать тот или иной режим открытия потока. Например, если поток, связанный с файлом, открывается только для чтения, выбирайте режим FileMode. Open. В этом случае будет открыт существующий файл. Если же файл открывается для записи, то вы можете либо открыть существующий файл, перезаписав его содержимое или дописав в него новые данные, а также создать новый файл.
Вклассе FileStream существует несколько конструкторов, позволяющих определить не только путь к файлу, но и режим его открытия, разрешенный доступ к файлу и режим совместного использования файла:
FileStream fs = new FileStream(name, FileMode.Open, FileAccess.Read);
FileStream fsi = new FileStream(name, FileMode.Open, FileAccess,Read, FileShare.Read);
Остановимся на режиме доступа к файлу, передаваемом приведенным выше конструкторам через третий параметр (табл. 2). Режим доступа задастся как статическая константа класса FileAccess.
|
Таблица 2: Режимы доступа FileAccess |
|
|
Режим |
Тип доступа |
|
|
Read |
Доступ только на чтение |
ReadWrite |
Доступ на чтение и запись |
Write |
Доступ на запись |
При необходимости программа может задать режим совместного использования файла, когда для одного и того же файла одновременно создается несколько потоков. Этот режим задается при помощи статических констант класса FileShare (табл. 3).
Таблица 3: Режимы совместного использования файла FileShare
Режим |
Тип доступа |
Inheritable |
Идентификатор файла может наследоваться дочерним процессом |
Запрет совместного использования файла. Любые дополнительные None запросы на открытие файла будут запрещены то тех пор, пока файл не
будет закрыт
Read |
Допускаются дополнительные запросы на открытие файла для чтения |
ReadWrite |
Допускаются дополнительные запросы на чтение и запись |
Write |
Допускаются дополнительные запросы на запись |
При необходимости программа может задать режим совместного использования файла, когда для одного и того же файла одновременно создается несколько потоков. Этот режим задается при помощи статических констант класса FileShare (табл. 3).
4.2 Открытие потоков BinaryWriter и BinaryReader
Итак, ваша программа создала поток на базе файла, воспользовавшись для этого классом FileStream. Далее на базе полученного таким образом потока необходимо создать потоки классов BinaryWriter и BinaryReader, предназначенные соответственно для записи в файл и чтения из файла двоичных данных:
BinaryWriter bw = new BinaryWriter(fs);
BinaryReader br = new BinaryReader(fs);
Работа с потоками BinaryWriter и BinaryReader будет описана чуть позже.
4.3 Закрытие потоков
После того как программа завершила работу с потоками, она должна их закрыть.
Для закрытия потоков используйте метод Close. Закрывайте потоки в порядке, обратном открытию. Если вы вначале открыли поток FileStream, а затем создали на его базе потоки класса BinaryWriter и BinaryReader, то закрывать потоки нужно в обратном порядке: вначале закройте потоки BinaryWriter и BinaryReader, а затем поток FileStream:
FileStream fs = new FileStream("myfile.dat", FileMode,CreateNew); BinaryWriter bw = new BinaryWriter(fs);
BinaryReader br = new BinaryReader(fs); //работа с потоками
bw.Close(); br.Close(); fs.Close();
4.4 Запись двоичных данных
Для записи двоичных данных в поток BinaryWriter предусмотрено несколько перегруженных методов Write, приведенных ниже:
public virtual void Write(bool); public virtual void Write(byte); public virtual void Write(byte[]); public virtual void Write(char); public virtual void Write(char[]); public virtual void Write(decimal); public virtual void Write(double); public virtual void Write(short); public virtual void Write(int); public virtual void Write(long); public virtual void Write(sbyte); public virtual void Write(float); public virtual void Write(uint); public virtual void Write(ulong); public virtual void Write(string);
public virtual void Write(byte[], int, int); public virtual void Write(char[], int, int);
Способ применения большинства перечисленных методов интуитивно ясен: для записи данных того или иного типа в файл вам нужно передать ссылку на экземпляр данных в качестве параметра методу Write. В зависимости от типа данных компилятор выберет нужную перегруженную версию этого метода.
При записи в двоичный поток текстовой строки string она будет предваряться префиксом, содержащим значение длины строки. Строка будет записана в кодировке ASCII.
Последние два перегруженных метода Write позволяют записать в поток массив
соответственно двоичных байтов и символов UNICODE. Второй параметр метода задает начальный индекс блока данных в массиве, а третий — размер блока (соответственно в байтах и символах).
4.5 Чтение двоичных данных
Чтобы прочитать данные из потока BinaryReader, нужно использовать один из методов, специально предусмотренных для этого в классе BinaryReader. Мы привели краткий список этих методов в табл. 4.
|
Таблица 4: Методы для чтения данных класса BinaryReader |
|
|
|
|
Метод |
Тип данных, считываемых из потока |
|
|
|
|
ReadBoolean |
Один объект типа Boolean |
|
ReadByte |
I байт данных |
|
ReadBytes |
Чтение заданного количества байтов данных |
|
ReadChar |
Один текстовый символ |
|
ReadChars |
Чтение заданного количества текстовых символов |
|
ReadDecimal |
Десятичное значение размером 16 байт |
|
ReadSingle |
Значение с плавающей десятичной точкой размером 4 байта |
|
ReadDouble |
Значение с плавающей десятичной точкой размером 8 байт |
|
ReadsByte |
Число со знаком размером 1 байт |
|
ReadlntlG |
Число со знаком размером 2 байта |
|
Readlnt32 |
Число со знаком размером 4 байта |
|
Readlnt64 |
Число со знаком размером 8 байт |
|
ReadUIntl6 |
Число без знака размером 2 байта |
|
ReadUInt32 |
Число без знака размером 4 байта |
|
ReadUInt64 |
Число без знака размером 8 байт |
|
ReadString |
Текстовая строка с префиксом длины |
4.6 Программа Binary
Программа Binary (листинг 2) демонстрирует некоторые приемы работы с двоичными файлами при помощи классов FileStream, BinaryWriter и BinaryReader.
Листинг 2.
using System; using System.10;
namespace Binary
{
class BinaryApp
{
private const string testFile = "mydata.dat";
static void Main(string[] args}
{
if(File,Exists(testFile))
{
Console.WriteLine("Файл {0} уже существует", testFile); Console.ReadLine();
return;
}
FileStream fs = new FileStream(testFile, FileMode.CreateNew); BinaryWriter bw = new BinaryWriter(fs); bw.Write("Text string");
for(uint i = 0; i < 20; i++)
{
bw.Write((uint)i);
}
bw.Closef); fs.Close();
fs = new FileStream(testFile, FileMode.Open, FileAccess.Read); BinaryReader br = new BinaryReader(fs);
string s = br.ReadString(); Console.WriteLine[s);
for (uint i = 0; i < 20; i+ + )
{
Console.Write("(0} ", br.ReadUInt32());
}
br.Close(); fs.Close(};
}
}
В самом начале своей работы программа проверяет существование файла с именем mydata.dat, используя для этого метод Exists класса File:
if (File.Exists[testFile})
{
Console.WriteLine("Файл (0} уже существует", testFile); Console.ReadLine();
return;
}
Класс File, предназначенный для работы с файлами и каталогами, мы рассмотрим позже. Пока же мы только заметим, что данный фрагмент кода проверяет существование файла mydata.dat в каталоге, который был текущим при запуске нашей программы на выполнение. Если такой файл там уже существует (например, был создан при предыдущем запуске этой программы), программа завершает свою работу с сообщением об ошибке.
Если же в текущем каталоге нет файла с именем mydata.dat, программа создает его, вызывая для этого конструктор класса FileStream с соответствующими параметрами:
FileStream fs = new FileStreamftestFile, FileMode.CreateNew);
Так как мы указали режим создания потока FileMode.CreateNew, то новый файл будет создан в том случае, если файла с таким именем нет в текущем каталоге.
После создания потока FileStream, связанного с файлом, мы создаем поток bw класса BinaryWriter, предназначенный для записи в файл двоичных данных:
BinaryWriter bw = new BinaryWriter(fs);
На следующем шаге программа записывает в поток текстовую строку:
bw.Write("Text string");
Далее программа записывает в поток bw 20 чисел типа uint:
for(uint i = 0; i < 20; i++)
{
bw.Write((uint)i);
}
После завершения записи оба потока закрываются методом Close:
bw.Close(); fs.Close();
На следующем этапе наша программа считывает содержимое только что записанного файла и отображает его на консоли.
Для этого она вначале открывает ноток fs класса FileStream, указывая, что файл должен быть открыт только для чтения:
fs = new FileStreamftestFile, FileMode.Open, FileAccess.Read);
Режим чтения задается константой FileAccess .Read.
Далее на базе потока f s программа создает поток Ьг класса BinaryReader:
BinaryReader br = new BinaryReader(fs);
Этот поток может быть использован для чтения из файла двоичных данных. Первым делом программа читает и отображает на консоли текстовую строку, записанную в файл:
string s = br.ReadString(); Console.WriteLine(s);
Далее она в цикле считывает 20 чисел и также отображает их на консоли:
for (uint i = 0; i < 20; i++)
{
Console.Write("{0}”, br.ReadUInt32());
}
Завершив работу с потоками, программа закрывает их:
br.Close(); fs.Close();
Далее она выводит на консоль сообщение об успешном создании файла и ждет, когда пользователь нажмет клавишу Enter, чтобы завершить свою работу:
Console.WriteLine("Файл успешно создан"); Console.ReadLine();