В классе BinaryWriter определены также три приведенных ниже варианта метода Read().
При неудачном исходе операции чтения эти методы генерируют исключение IOException. Кроме того, в классе BinaryReader определен стандартный метод Close(). Метод Описание int Read() Возвращает целочисленное представление следующего доступного символа из вызывающего потока ввода. При обнаружении конца файла возвращает значение -1 int Read(byte[] buffer, int offset, int count) Делает попытку прочитать количество count байтов в массив buffer, начиная с элемента buffer[offset], и возвращает количество успешно считанных байтов int Read(char[]buffer, int offset, int count) Делает попытку прочитать количество count символов в массив buffer, начиная с элемента buffer[offset], и возвращает количество успешно считанных символов Демонстрирование двоичного ввода-вывода
Ниже приведен пример программы, в котором демонстрируется применение клас сов BinaryReader и BinaryWriter для двоичного ввода-вывода. В этой программе в файл записываются и считываются обратно данные самых разных типов. // Записать двоичные данные, а затем считать их обратно. using System; using System.IO; class RWData { static void Main() { BinaryWriter dataOut; BinaryReader dataIn; int i = 10; double d = 1023.56; bool b = true; string str = "Это тест"; // Открыть файл для вывода. try { dataOut = new BinaryWriter(new FileStream("testdata", FileMode.Create)); } catch(IOException exc) { Console.WriteLine("Ошибка открытия файла:\n" + exc.Message); return; } // Записать данные в файл. try { Console.WriteLine("Запись " + i); dataOut.Write(i); Console.WriteLine("Запись " + d); dataOut.Write(d); Console.WriteLine("Запись " + b); dataOut.Write(b); Console.WriteLine("Запись " + 12.2 * 7.4); dataOut.Write(12.2 * 7.4); Console.WriteLine("Запись " + str); dataOut.Write(str); } catch(IOException exc) { Console.WriteLine("Ошибка ввода-вывода:\n" + exc.Message); } finally { dataOut.Close(); } Console.WriteLine(); // А теперь прочитать данные из файла. try { dataIn = new BinaryReader(new FileStream("testdata", FileMode.Open)); } catch(IOException exc) { Console.WriteLine("Ошибка открытия файла:\n" + exc.Message); return; } try { i = dataIn.ReadInt32(); Console.WriteLine("Чтение " + i); d = dataIn.ReadDouble(); Console.WriteLine("Чтение " + d); b = dataIn.ReadBoolean () ; Console.WriteLine("Чтение " + b); d = dataIn.ReadDouble(); Console.WriteLine("Чтение " + d); str = dataIn.ReadString(); Console.WriteLine("Чтение " + str); } catch(IOException exc) { Console.WriteLine("Ошибка ввода-вывода:\n" + exc.Message); } finally { dataIn.Close(); } } }
Вот к какому результату приводит выполнение этой программы. Запись 10 Запись 1023.56 Запись True Запись 90.28 Запись Это тест Чтение 10 Чтение 1023.56 Чтение True Чтение 90.28 Чтение Это тест
Если просмотреть содержимое файла testdata, который получается при выпол нении этой программы, то можно обнаружить, что он содержит данные в двоичной, а не в удобочитаемой текстовой форме.
Далее следует более практический пример, демонстрирующий, насколько эффек тивным может быть двоичный ввод-вывод. Для учета каждого предмета хранения на складе в приведенной ниже программе сначала запоминается наименование предме та, имеющееся в наличии, количество и стоимость, а затем пользователю предлагается ввести наименование предмета, чтобы найти его в базе данных. Если предмет найден, отображаются сведения о его запасах на складе. /* Использовать классы BinaryReader и BinaryWriter для реализации простой программы учета товарных запасов. */ using System; using System.IO; class Inventory { static void Main() { BinaryWriter dataOut; BinaryReader detaIn; string item; // наименование предмета int onhand; // имеющееся в наличии количество double cost; // цена try { dataOut = new BinaryWriter(new FileStream("inventory.dat", FileMode.Create)); } catch(IOException exc) { Console.WriteLine("He удается открыть файл " + "товарных запасов для вывода"); Console.WriteLine("Причина: " + exc.Message); return; } // Записать данные о товарных запасах в файл. try { dataOut.Write("Молотки"); dataOut.Write(10); dataOut.Write(3.95); dataOut.Write("Отвертки"); dataOut.Write(18); dataOut.Write(1.50); dataOut.Write("Плоскогубцы"); dataOut.Write(5); dataOut.Write(4.95); dataOut.Write("Пилы"); dataOut.Write(8); dataOut.Write(8.95); } catch(IOException exc) { Console.WriteLine("Ошибка записи в файл товарных запасов"); Console.WriteLine("Причина: " + exc.Message); } finally { dataOut.Close(); } Console.WriteLine(); // А теперь открыть файл товарных запасов для чтения. try { dataIn = new BinaryReader(new FileStream("inventory.dat", FileMode.Open)); } catch(IOException exc) { Console.WriteLine("He удается открыть файл " + "товарных запасов для ввода"); Console.WriteLine("Причина: " + exc.Message); return; } // Найти предмет, введенный пользователем. Console.Write("Введите наименование для поиска: "); string what = Console.ReadLine(); Console.WriteLine(); try { for(;;) { // Читать данные о предмете хранения. item = dataIn.ReadString(); onhand = dataIn.ReadInt32(); cost = dataIn.ReadDouble(); // Проверить, совпадает ли он с запрашиваемым предметом. // Если совпадает, то отобразить сведения о нем. if(item.Equals(what, StringComparison.OrdinalIgnoreCase)) { Console.WriteLine(item + ": " + onhand + " штук в наличии. " + "Цена: {0:С} за штуку", cost); Console.WriteLine("Общая стоимость по наименованию <{0}>: {1:С}.", item, cost * onhand); break; } } } catch(EndOfStreamException) { Console.WriteLine("Предмет не найден."); } catch(IOException exc) { Console.WriteLine("Ошибка чтения из файла товарных запасов"); Console.WriteLine("Причина: " + exc.Message); } finally { dataIn.Glose(); } } }
Выполнение этой программы может привести, например, к следующему результату. Введите наименование для поиска: Отвертки Отвертки: 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