Опубликован: 05.08.2010 | Уровень: специалист | Доступ: платный
Лекция 6:

Работа с потоками данных

< Лекция 5 || Лекция 6: 12345678910111213

Упражнение 4. Сжатие данных в потоке FileStream

В данном упражнении познакомимся с техникой чтения/записи сжатых данных в файловом потоке FileStream, хотя то же самое будет справедливо и для некоторых других рабочих потоков. Так же как и ранее, с файлом будет связываться поток FileStream, но формат данных в потоке будут обеспечивать специализированные классы сборки System.dll:

  1. System.IO.Compression.DeflateStream
  2. System.IO.Compression. GZipStream

Оба класса-надстройки реализуют эффективные алгоритмы сжатия без потерь информации.

  • Командой File/Add/New Project добавьте к решение новый проект согласно следующему снимку мастера

  • В панели Solution Explorer вызовите контекстное меню для узла App4 и выполните команду Set as StartUp Project, чтобы назначить проект стартовым
  • В панели Solution Explorer выделите узел App4 и щелкните на пиктограмме Properties, чтобы открыть панель конструктора проектов
  • На вкладке Application установите выпадающий список Output type в значение Console Application, включив параллельно окну формы еще и консоль для вывода информации
  • В режиме Design конструктора форм поместите на форму из панели Toolbox четыре экземпляра кнопки LinkLabel, две текстовые метки Label и настройте их в соответствии с таблицей свойств
Таблица 19.3.
Элемент Свойство Значение
Form Text Сжимающие потоки
LinkLabel (Name) saveDeflate
Text Compression DeflateStream and save to file
LinkLabel (Name) loadDeflate
Text Load file and decompression DeflateStream
LinkLabel (Name) saveGZip
Text Compression GZipStream and save to file
LinkLabel (Name) loadGZip
Text Load file and decompression GZipStream

Интерфейс формы должен выглядеть так


Мы подготовили интерфейс пользователя, который больше менять не будем и его следует замкнуть.

  • В режиме Design конструктора форм выполните команду Edit/Select All, а затем - команду Format/Lock Controls, элементы интерфейса станут недоступными для редактирования в графическом режиме проектирования
  • Последовательно двойным щелчком на каждом экземпляре объекта LinkLabel создайте заготовки обработчиков и их регистрацию для событий LinkClicked

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

  • Щелкните правой кнопкой мыши в любом месте формы Form1 (или вне формы) и выполните команду View Code, чтобы вызвать редактор процедурного кода
  • Заполните файл Form1.cs следующим образом (код файла приведен полностью)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
    
// Дополнительные пространства имен
using System.IO.Compression;
using System.IO;
    
namespace App4
{
    public partial class Form1 : Form
    {
        String fileNameDeflate = "DataDeflate.dat";
        String fileNameGZip = "DataGZip.dat";
        String messageDeflate = "Привет всем от DeflateStream!";
        String messageGZip = "Привет всем от GZipStream!";
        const Int32 lenBuf = 4;
    
        public Form1()
        {
            InitializeComponent();
    
            // Настраиваем консоль
            Console.ForegroundColor = ConsoleColor.White;
            Console.Title = "Упражнение 4. Сжатие данных в потоке FileStream";
            Console.CursorVisible = false;
        }
    
        private void saveDeflate_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            using (FileStream fs = new FileStream(fileNameDeflate, FileMode.Create))
            {
                // Погружаем файловый поток в оболочку сжимающего потока
                DeflateStream compress = new DeflateStream(fs, CompressionMode.Compress);
                // Преобразуем строковые данные в массив байт кодировки UTF-16
                byte[] bytes = new UnicodeEncoding().GetBytes(messageDeflate);
                // Записываем данные в файл через сжимающий поток
                compress.Write(bytes, 0, bytes.Length);
    
                // Сливаем кэш и закрываем
                compress.Flush();
                compress.Close();
            }
        }
    
        private void loadDeflate_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (!new FileInfo(fileNameDeflate).Exists)
            {
                MessageBox.Show("Нет данных для отображения",
                    "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
    
            using (FileStream fs = new FileStream(fileNameDeflate, FileMode.Open, FileAccess.Read))
            {
                // Создаем буфер нужной длины, обычно 1024b (1Кb)
                byte[] buffer = new byte[lenBuf / 2 * 2];// Округляем до четного
                // Погружаем файловый поток в оболочку декомпрессионного потока
                DeflateStream decompress = new DeflateStream(fs, CompressionMode.Decompress);
    
                int count = 0;
                String msg = String.Empty;
                // Читаем все данные через декомпрессионный поток
                while ((count = decompress.Read(buffer, 0, buffer.Length)) != 0)
                    msg += new UnicodeEncoding().GetString(buffer, 0, count);
    
                // Сливаем кэш и закрываем
                decompress.Flush();
                decompress.Close();
    
                // Печатаем прочитанное 
                Console.Clear();
                Console.WriteLine(msg);
            }
        }
    
        private void saveGZip_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            using (FileStream fs = new FileStream(fileNameGZip, FileMode.Create))
            {
                // Погружаем файловый поток в оболочку сжимающего потока
                GZipStream compress = new GZipStream(fs, CompressionMode.Compress);
                // Преобразуем строковые данные в массив байт кодировки UTF-16
                byte[] bytes = new UnicodeEncoding().GetBytes(messageGZip);
                // Записываем данные в файл через сжимающий поток
                compress.Write(bytes, 0, bytes.Length);
    
                // Сливаем кэш и закрываем
                compress.Flush();
                compress.Close();
            }
        }
    
        private void loadGZip_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (!new FileInfo(fileNameGZip).Exists)
            {
                MessageBox.Show("Нет данных для отображения",
                    "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
    
            using (FileStream fs = new FileStream(fileNameGZip, FileMode.Open, FileAccess.Read))
            {
                // Создаем буфер нужной длины, обычно 1024b (1Кb)
                byte[] buffer = new byte[lenBuf / 2 * 2];// Округляем до четного
                // Погружаем файловый поток в оболочку декомпрессионного потока
                GZipStream decompress = new GZipStream(fs, CompressionMode.Decompress);
    
                int count = 0;
                String msg = String.Empty;
                // Читаем все данные через декомпрессионный поток
                while ((count = decompress.Read(buffer, 0, buffer.Length)) != 0)
                    msg += new UnicodeEncoding().GetString(buffer, 0, count);
    
                // Сливаем кэш и закрываем
                decompress.Flush();
                decompress.Close();
    
                // Печатаем прочитанное 
                Console.Clear();
                Console.WriteLine(msg);
            }
        }
    }
}
  • Испытайте работу приложения и разберитесь с кодом

Один из результатов будет таким


Упражнение 5. Буферизованная обертка BufferedStream

Когда создается рабочий поток, связанный с каким-либо устройством, он работает синхронно с кодом приложения и для медленных устройств приложение будет ждать завершения соответствующих операций ввода/вывода данных. Буферизация таких операций в кэше памяти, где данные могут группироваться в большие порции, может существенно ускорить работу приложения. Для этого и существует обертка BufferedStream, в которую можно погрузить любой основной поток. Тогда все манипуляции с данными будут выполняться с помощью этого вспомогательного инструмента.

Рассмотрим работу с буферизованным потоком на примере его совместного использования с файловым потоком FileStream.

  • Командой File/Add/New Project создайте новый проект App5 типа консольного приложения и назначьте его стартовым

  • Заполните файл Program.cs следующим кодом
using System;
using System.Collections.Generic;
using System.Text;
    
// Дополнительные пространства имен
using System.IO;
    
namespace App5
{
    class Program
    {
        static void Main(string[] args)
        {
            // Настраиваем консоль
            Console.ForegroundColor = ConsoleColor.White;
            Console.Title = "Упражнение 5. Буферизованная обертка BufferedStream";
    
            String fileName = "DataBuffered.dat";
            String message = "Привет всем от BufferedStream!";
    
            FileMode mode = FileMode.Create;
            if (File.Exists(fileName))
                mode = FileMode.Append;
    
            // Пишем
            using (FileStream fs = new FileStream(fileName, mode))
            {
                using (BufferedStream bs = new BufferedStream(fs, 1024 * 4))
                {
                    // Применим кодировку, по умолчанию установленную в Windows
                    byte[] bytes = Encoding.Default.GetBytes(message);
                    // Дописываем побайтно в обертку
                    for (int i = 0; i < bytes.Length; i++)
                        bs.WriteByte(bytes[i]);
                    // Дополняем символами новой строки "\r\n"
                    bs.Write(Encoding.Default.
                        GetBytes(Environment.NewLine), 0, Environment.NewLine.Length);
                    /********** Вариант не для режима Append **********
                        // Записываем побайтно в обертку
                        while (bs.Position < bytes.Length)
                            bs.WriteByte(bytes[bs.Position]);
                    //************************************************/
                }
            }
    
            // Читаем
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                fs.Seek(0, SeekOrigin.Begin);// Необязательно
    
                using (BufferedStream bs = new BufferedStream(fs))// По умолчанию 4096 bytes
                {
                    byte[] bytes = new byte[bs.Length];
    
                    int result, i = 0;
                    while ((result = bs.ReadByte()) != -1)
                        bytes[i++] = (byte)result;
                    /*********** Вариант ****************
                        while (bs.Position < bytes.Length)
                            bytes[bs.Position] = (byte)bs.ReadByte();
                    //**********************************/
    
                    // Печатаем результат
                    String msg = Encoding.Default.GetString(bytes);
                    Console.Clear();
                    Console.WriteLine(msg);
                }
            }
    
            Console.ReadLine();// Тормозим консольное окно
        }
    }
}

По умолчанию величина буфера в BufferedStream установлена размером 4 Kb, но в перегрузке конструктора мы вольны установить ее любой. В приведенном коде при каждом запуске информация записывается в файл в режиме добавления. Потоки мы явно не закрываем, поскольку используем инструкцию using().

  • Запустите приложение и испытайте его работу, разберитесь с кодом

Для нескольких запусков результат будет таким


Упражнение 6. Классы-обертки BinaryWriter и BinaryReader для работы с встроенными типами C#

Встроенными (элементарными, примитивными, базовыми, стандартными, простыми) типами C# называются типы, которые имеют ключевые слова в самом языке как псевдонимы ( alias ). Например, Int32 - int, Single - float, String - string, Double - double, Boolean - bool и т.д. Все они отностятся к типам значений и выведены из абстрактного класса ValueType. Такие типы являются стэковыми, т.е. в стэк функций при выполнении программы помещаются не адресные переменные, а сами данные. Они представляют собой структуры и обозначаются отдельными пиктограммами в панели Object Browser


Для чтения и записи таких данных в байтовый поток используются обертки BinaryReader и BinaryWriter пространства имен System.IO библиотечной сборки mscorlib.dll (специализированные читатель и писатель). Эти классы расщепляют базовый тип на последовательность байт и передают ее прикрепленному основному потоку для чтения/записи в связанное с потоком устройство.

Рассмотрим работу этих оберток на примере разнотипных данных, которые мы ранее уже использовали во втором упражнении. По прежнему, в качестве рабочего будем использовать файловый поток FileStream.

  • Добавьте к решению проект консольного приложения App6 и назначьте его стартовым
  • Заполните файл Program.cs следующим кодом
using System;
using System.Collections.Generic;
using System.Text;
    
// Дополнительные пространства имен
using System.IO;// Для потоков
    
namespace App6
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.Title = "Упражнение 6. Двоичный ввод/вывод";
            Console.CursorVisible = false;
    
            new MyClass().Foo();
    
            Console.ReadLine();// Задержка консоли
        }
    }
}
    
namespace App6
{
    class MyClass
    {
        public void Foo()
        {
            // Сохраняемые данные
            string sData1 = "Привет всем!";
            string sData2 = "Привет студентам!";
            int iData1 = (int)12345;      
            int iData2 = int.MaxValue;     
            float fData = 67F;             
            double dData = 89D;           
    
            // Сохраняем значения в файле
            String fileName = "Data.dat";// Будет создан в каталоге сборки
    
            // Объявляем рабочий поток
            FileStream fs = null;
            try
            {
                // Создаем или усекаем файл
                fs = new FileStream(fileName, FileMode.Create);
                // Подключаем к писателю
                BinaryWriter data = new BinaryWriter(fs);
                // Пишем данные, компилятор по типу сам 
                // определяет нужную перегрузку метода
                data.Write(sData1);
                data.Write(sData2);
                data.Write(iData1);
                data.Write(iData2);
                data.Write(fData);
                data.Write(dData);
    
                fs.Flush(); // Сплюнуть кэш (необязательно!)
            }
            finally
            {
                if (fs != null)
                    fs.Close();
            }
    
            // Читаем значения из файла и печатаем их на консоль
            try
            {
                // Открываем только для чтения
                fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                if (fs.CanSeek) // Поддерживает ли такую возможность
                    fs.Seek(0L, SeekOrigin.Begin);  // Необязательно!
    
                // Подключаем к читателю, считываем и печатаем
                BinaryReader data = new BinaryReader(fs);
                // Порядок чтения должен строго соответствовать порядку записи
                Console.WriteLine(data.ReadString());
                Console.WriteLine(data.ReadString());
                Console.WriteLine(data.ReadInt32());
                Console.WriteLine(data.ReadInt32());
                Console.WriteLine(data.ReadSingle());
                Console.WriteLine(data.ReadDouble());
    
                // Измеряем длину потока, пока открыт
                Console.WriteLine("==============\nВсего байт: {0}", fs.Length);
            }
            finally
            {
                fs.Close();
            }
    
            // Измеряем длину файла
            Console.WriteLine("Длина файла: {0}", new FileInfo(fileName).Length);
        }
    }
}
  • Разберитесь с кодом и запустите приложение - результат будет таким

Не стоит думать, что данные при записи разбиваются объектом BinaryWriter по такой же схеме, как мы это делали во втором упражнении. Если попробовать записать с помощью этого писателя, а прочитать побайтно как в Упражнении 2, то ничего не получится: у рассмотренных читателя и писателя свои форматы данных. Хотя, расщипление на байты соответствует длине типов (кроме string ), например, тип int состоит из 4 байтов и точно такой же длинной будет записан в поток.

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

В данном упражнении мы продемонстрировали только простейшие возможности объектов BinaryReader и BinaryWriter. Они могут применяться и для реализации более сложных схем чтения/записи. Для этого имеется все необходимое: перегрузки конструкторов, методы последовательного доступа, методы сдвига указателя. Но мы их рассматривать не будем, поскольку наша основная задача - выполнить обзор потоков как можно шире, а впереди еще много вкусного.

< Лекция 5 || Лекция 6: 12345678910111213
Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?

Dmitriy Ivanchenko
Dmitriy Ivanchenko
Украина, Кировоград, Виктория-П, 2011
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974