Опубликован: 19.02.2009 | Доступ: свободный | Студентов: 2757 / 570 | Оценка: 4.35 / 4.11 | Длительность: 16:28:00
ISBN: 978-5-94774-401-9
Лекция 11:

Организация С#-системы ввода-вывода

< Лекция 10 || Лекция 11: 123 || Лекция 12 >

Двоичные потоки

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

Выходной поток BinaryWriter поддерживает произвольный доступ, т.е. имеется возможность выполнять запись в произвольную позицию двоичного файла. Наиболее важные методы потока BinaryWriter:

Член класса Описание
BaseStream Определяет базовый поток, с которым работает объект BinaryWriter
Close Закрывает поток
Flush Очищает буфер
Seek Устанавливает позицию в текущем потоке
Write Записывает значение в текущий поток

Наиболее важные методы выходного потока BinaryReader:

Член класса Описание
BaseStream Определяет базовый поток, с которым работает объект BinaryReader
Close Закрывает поток
PeekChar Возвращает следующий символ потока без перемещения внутреннего указателя в потоке
Read Считывает очередной поток байтов или символов и сохраняет в массиве, передаваемом во входном параметре
ReadBoolean, ReadByte, ReadInt32 и т.д Считывает из потока данные определенного типа

Двоичный поток открывается на основе базового потока (например, FileStream ), при этом двоичный поток будет преобразовывать байтовый поток в значения int -, double -, short - и т.д.

Рассмотрим пример формирования двоичного файла:

static void Main()
  {
    //открываем двоичный поток    
    BinaryWriter fOut=new BinaryWriter(new FileStream("t.dat",FileMode.Create));
    //записываем данные в двоичный поток    
    for (int i=0; i<=100; i+=2)
    {
     fOut.Write(i);
    }
    fOut.Close(); //закрываем двоичный поток
  }

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

static void Main()
  {
     FileStream f=new FileStream("t.dat",FileMode.Open);
    BinaryReader fIn=new BinaryReader(f);
    long n=f.Length/4; //определяем количество чисел в двоичном потоке
    int a;
    for (int i=0; i<n; i++)
    {
     a=fIn.ReadInt32();
     Console.Write(a+" ");
    }
    fIn.Close();
    f.Close();
  }

Двоичные файлы являются файлами с произвольным доступом, при этом нумерация элементов в двоичном файле ведется с нуля. Произвольный доступ обеспечивает метод Seek. Рассмотрим его синтаксис:

Seek(long newPos, SeekOrigin pos)

где параметр newPos определяет новую позицию внутреннего указателя файла в байтах относительно исходной позиции указателя, которая определяется параметром pos. В свою очередь параметр pos должен быть задан одним из значений перечисления SeekOrigin:

Значение Описание
SeekOrigin.Begin Поиск от начала файла
SeekOrigin.Current Поиск от текущей позиции указателя
SeekOrigin.End Поиск от конца файла

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

Рассмотрим пример организации произвольного доступа к двоичному файлу (на примере файла t.dat ):

static void Main()
  {
    //изменение данных в двоичном потоке
    FileStream f=new FileStream("t.dat",FileMode.Open);
    BinaryWriter fOut=new BinaryWriter(f);
    long n=f.Length; //определяем количество байт в байтовом потоке
    int a;
    for (int i=0; i<n; i+=8) //сдвиг на две позиции, т.к. тип int занимает 4 байта
    {
     fOut.Seek(i,SeekOrigin.Begin);
     fOut.Write(0);
    }
    fOut.Close();
    //чтение данных из двоичного потока
    f=new FileStream("t.dat",FileMode.Open);
    BinaryReader fIn=new BinaryReader(f);
    n=f.Length/4; //определяем количество чисел в двоичном потоке
    for (int i=0; i<n; i++)
    {
     a=fIn.ReadInt32();
     Console.Write(a+" ");
    }
    fIn.Close();
    f.Close();
 }

Поток BinaryReader не имеет метода Seek, однако используя возможности потока FileStream можно организовать произвольный доступ при чтении двоичных файлов. Рассмотрим следующий пример:

static void Main()
{
 //Записываем в файл t.dat целые числа от 0 до 100 
 FileStream f=new FileStream("t.dat",FileMode.Open);
 BinaryWriter fOut=new BinaryWriter(f);
 for (int i=0; i<100; ++i) 
 {
  fOut.Write(i);
 }
 fOut.Close();
 //Объекты f и fIn связаны с одним и тем же файлом 
 f=new FileStream("t.dat",FileMode.Open);
 BinaryReader fIn=new BinaryReader(f);
 long n=f.Length; //определяем количество байт потоке
 //Читаем данные из файла t.dat, перемещая внутренний указатель на 8 байт, т.е. на два целых числа
 for (int i=0; i<n; i+=8)
 {
  f.Seek(i,SeekOrigin.Begin); 
  int a=fIn.ReadInt32();
  Console.Write(a+" ");
 }
 fIn.Close();
 f.Close();
}
Перенаправление стандартных потоков

Тремя стандартными потоками, доступ к которым осуществляется через свойства Console.Out, Console.In и Console.Error, могут пользоваться все программы, работающие в пространстве имен System. Свойство Console.Out относится к стандартному выходному потоку. По умолчанию это консоль. Например, при вызове метода Console.WriteLine() информация автоматически передается в поток Console.Out. Свойство Console.In относится к стандартному входному потоку, источником которого по умолчанию является клавиатура. Например, при вводе данных с клавиатуры информация автоматически передается потоку Console.In, к которому можно обратиться с помощью метода Console.ReadLine(). Свойство Console.Error относится к ошибкам в стандартном потоке, источником которого также по умолчанию является консоль. Однако эти потоки могут быть перенаправлены на любое совместимое устройство ввода-вывода, например, на работу с физическими файлами.

Перенаправить стандартный поток можно с помощью методов SetIn(), SetOut() и SetError(), которые являются членами класса Console:

static void Setln(TextReader input)
static void SetOut(TextWriter output)
static void SetError(TextWriter output)

Пример перенаправления потоков проиллюстрирован следующей программой, в которой двумерный массив вводится из файла input.txt, а выводится в файл output.txt

static void Main()
{
 try
 {
  int[,] MyArray;
  StreamReader file=new StreamReader("input.txt"); 
  Console.SetIn(file);  // перенаправляем стандартный входной поток на file
  string line=Console.ReadLine();
  string []mas=line.Split(' ');
  int n=int.Parse(mas[0]);
int m=int.Parse(mas[1]);
  MyArray = new int[n,m];
  for (int i = 0; i < n; i++)
  {
   line = Console.ReadLine();
   mas = line.Split(' ');
   for (int j = 0; j < m; j++)
   {
    MyArray[i,j] = int.Parse(mas[j]);
   }
  }
  PrintArray("исходный массив:", MyArray, n, m);
  file.Close();
}

static void PrintArray(string a, int[,] mas, int n, int m)
{
 StreamWriter file=new StreamWriter("output.txt"); // перенаправляем стандартный входной поток на file
 Console.SetOut(file);
 Console.WriteLine(a);
 for (int i = 0; i < n; i++)
 {
  for (int j=0; j<m; j++) Console.Write("{0} ", mas[i,j]);
  Console.WriteLine();
 }
 file.Close();
}

___input.txt_________
3 4
1 4 2 8
4 9 0 1
5 7 4 2

При необходимости восстановить исходное состояние потока Console.In можно следующим образом:

TextWriter str = Console.In;  // первоначально сохраняем исходное состояние входного потока
…
Console.SetIn(str);    // при необходимости восстанавливаем исходное состояние входного потока

Аналогичным образом можно восстановить исходное состояние потока Console.Out:

TextWriter str = Console.Out;  // первоначально сохраняем исходное состояние выходного потока
…
// при необходимости восстанавливаем исходное состояние выходного потока
Console.SetOut(str);
Задание. Подумайте для чего нужно два потока Console.Out и Console.Error, если они оба при стандартной работе выводят информацию на экран.
< Лекция 10 || Лекция 11: 123 || Лекция 12 >