Выполнение этой программы может привести, например, к следующему результату.Введите наименование для поиска: ОтверткиОтвертки: 18 штук в наличии. Цена: $1.50 за штуку.Общая стоимость по наименованию <Отвертки>: $27.00.
Обратите внимание на то, что сведения о товарных запасах сохраняются в этой программе в двоичном формате, а не в удобной для чтения текстовой форме. Благодаряэтому обработка числовых данных может выполняться без предварительного их преобразования из текстовой формы.
Обратите также внимание на то, как в этой программе обнаруживается конец файла. Методы двоичного ввода генерируют исключение EndOfStreamException по достижении конца потока, и поэтому файл читается до тех пор, пока не будет найденискомый предмет или сгенерировано данное исключение. Таким образом, для обнаружения конца файла никакого специального механизма не требуется.Файлы с произвольным доступом
В предыдущих примерах использовались последовательные файлы, т.е. файлы сострого линейным доступом, байт за байтом. Но доступ к содержимому файла можетбыть и произвольным. Для этого служит, в частности, метод Seek(), определенныйв классе FileStream. Этот метод позволяет установить указатель положения в файле,или так называемый указатель файла, на любое место в файле. Ниже приведена общаяформа метода Seek():long Seek(long offset, SeekOrigin origin)
где offset обозначает новое положение указателя файла в байтах относительно заданного начала отсчета (origin). В качестве origin может быть указано одно из приведенных ниже значений, определяемых в перечислении SeekOrigin.ЗначениеОписаниеSeekOrigin.BeginПоиск от начала файлаSeekOrigin.CurrentПоиск от текущего положенияSeekOrigin.EndПоиск от конца файла
Следующая операция чтения или записи после вызова метода Seek() будет выполняться, начиная с нового положения в файле, возвращаемого этим методом. Если вовремя поиска в файле возникает ошибка, то генерируется исключение IOException.Если же запрос положения в файле не поддерживается базовым потоком, то генерируется исключение NotSupportedException. Кроме того, могут быть сгенерированыи другие исключения.
В приведенном ниже примере программы демонстрируется ввод-вывод в файл спроизвольным доступом. Сначала в файл записываются прописные буквы английского алфавита, а затем его содержимое считывается обратно в произвольном порядке.// Продемонстрировать произвольный доступ к файлу.using System;using System.IO;class RandomAccessDemo { static void Main() { FileStream f = null; char ch; try { f = new FileStream("random.dat", FileMode.Create); // Записать английский алфавит в файл. for (int i=0; i < 26; i++) f.WriteByte((byte)('A'+i)); // А теперь считать отдельные буквы английского алфавита. f.Seek(0, SeekOrigin.Begin); // найти первый байт ch = (char) f.ReadByte(); Console.WriteLine("Первая буква: " + ch); f.Seek(1, SeekOrigin.Begin); // найти второй байт ch = (char) f.ReadByte(); Console.WriteLine("Вторая буква: " + ch); f.Seek(4, SeekOrigin.Begin); // найти пятый байт ch = (char) f.ReadByte(); Console.WriteLine("Пятая буква: " + ch); Console.WriteLine (); // А теперь прочитать буквы английского алфавита через одну. Console.WriteLine("Буквы алфавита через одну: "); for(int i=0; i < 26; i += 2) { f.Seek(i, SeekOrigin.Begin); // найти i-й символ ch = (char) f.ReadByte(); Console.Write(ch + " "); } } catch(IOException exc) { Console.WriteLine("Ошибка ввода-вывода\n" + exc.Message); } finally { if(f != null) f.Close(); } Console.WriteLine(); }}
При выполнении этой программы получается следующий результат.Первая буква: АВторая буква: ВПятая буква: ЕБуквы алфавита через одну:А C E G I K M O Q S U W Y
Несмотря на то что метод Seek() имеет немало преимуществ при использованиис файлами, существует и другой способ установки текущего положения в файле с помощью свойства Position. Как следует из табл. 14.2, свойство Position доступнокак для чтения, так и для записи. Поэтому с его помощью можно получить или жеустановить текущее положение в файле. В качестве примера ниже приведен фрагменткода из предыдущей программы записи и чтения из файла с произвольным доступом random.dat, измененный с целью продемонстрировать применение свойстваPosition.Console.WriteLine("Буквы алфавита через одну: ");for(int i=0; i < 26; i += 2) { f.Position = i; // найти i-й символ посредством свойства Position ch = (char) f.ReadByte(); Console.Write(ch + " ");}Применение класса MemoryStream
Иногда оказывается полезно читать вводимые данные из массива или записыватьвыводимые данные в массив, а не вводить их непосредственно из устройства или выводить прямо на него. Для этой цели служит класс MemoryStream. Он представляетсобой реализацию класса Stream, в которой массив байтов используется для вводаи вывода. В классе MemoryStream определено несколько конструкторов. Ниже представлен один из них:MemoryStream(byte[] buffer)
где buffer обозначает массив байтов, используемый в качестве источника или адресата в запросах ввода-вывода. Используя этот конструктор, следует иметь в виду,что массив buffer должен быть достаточно большим для хранения направляемыхв него данных.
В качестве примера ниже приведена программа, демонстрирующая применениекласса MemoryStream в операциях ввода-вывода.// Продемонстрировать применение класса MemoryStream.using System;using System.IO;class MemStrDemo { static void Main() { byte[] storage = new byte[255]; // Создать запоминающий поток. MemoryStream memstrm = new MemoryStream(storage); // Заключить объект memstrm в оболочки классов // чтения и записи данных в потоки. StreamWriter memwtr = new StreamWriter(memstrm); StreamReader memrdr = new StreamReader(memstrm); try { // Записать данные в память, используя объект memwtr. for(int i=0; i < 10; i++) memwtr.WriteLine("byte [" + i + "]: " + i); // Поставить в конце точку. memwtr.WriteLine("."); memwtr.Flush(); Console.WriteLine("Чтение прямо из массива storage: "); // Отобразить содержимое массива storage непосредственно, foreach(char ch in storage) { if (ch == '.') break; Console.Write(ch); } Console.WriteLine("\nЧтение из потока с помощью объекта memrdr: "); // Читать из объекта memstrm средствами ввода данных из потока. memstrm.Seek(0, SeekOrigin.Begin); // установить указатель файла // в исходное положение string str = memrdr.ReadLine(); while(str != null) { str = memrdr .ReadLine(); if (str[0] == '.') break; Console.WriteLine(str); } } catch(IOException exc) { Console.WriteLine("Ошибка ввода-вывода\n" + exc.Message); } finally { // Освободить ресурсы считывающего и записывающего потоков. memwtr.Close(); memrdr.Close(); } }}
Вот к какому результату приводит выполнение этой программы.Чтение прямо из массива storage:byte [0]: 0byte [1]: 1byte [2]: 2byte [3]: 3byte [4]: 4byte [5]: 5byte [6]: 6byte [7]: 7byte [8]: 8byte [9]: 9Чтение из потока с помощью объекта memrdr:byte [1]: 1byte [2]: 2byte [3]: 3byte [4]: 4byte [5]: 5byte [6]: 6byte [7]: 7byte [8]: 8byte [9]: 9
В этой программе сначала создается массив байтов, называемый storage. Затемэтот массив используется в качестве основной памяти для объекта memstrm классаMemoryStream. Из объекта memstrm, в свою очередь, создаются объекты memrdr класса StreamReader и memwtr класса StreamWriter. С помощью объекта memwtr выводимые данные записываются в запоминающий поток. Обратите внимание на то, чтопосле записи выводимых данных для объекта memwtr вызывается метод Flush(). Этонеобходимо для того, чтобы содержимое буфера этого объекта записывалось непосредственно в базовый массив. Далее содержимое базового массива байтов отображается вручную в цикле foreach. После этого указатель файла устанавливается с помощью метода Seek() в начало запоминающего потока, из которого затем вводятсяданные с помощью объекта потока memrdr.
Запоминающие потоки очень полезны для программирования. С их помощьюможно, например, организовать сложный вывод с предварительным накоплениемданных в массиве до тех пор, пока они не понадобятся. Этот прием особенно полезен для программирования в такой среде с графическим пользовательским интерфейсом, как Windows. Кроме того, стандартный поток может быть переадресованиз массива. Это может пригодиться, например, для подачи тестовой информации впрограмму.Применение классов StringReader и StringWriter