При выполнении в лабораторной работе упражнения №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" или один из зависимых от них компонентов. Не удается найти указанный файл. Делаю все пунктуально. В чем может быть проблема? |
Работа с потоками данных
Упражнение 4. Сжатие данных в потоке FileStream
В данном упражнении познакомимся с техникой чтения/записи сжатых данных в файловом потоке FileStream, хотя то же самое будет справедливо и для некоторых других рабочих потоков. Так же как и ранее, с файлом будет связываться поток FileStream, но формат данных в потоке будут обеспечивать специализированные классы сборки System.dll:
- System.IO.Compression.DeflateStream
- 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 и настройте их в соответствии с таблицей свойств
Интерфейс формы должен выглядеть так
Мы подготовили интерфейс пользователя, который больше менять не будем и его следует замкнуть.
- В режиме 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. Они могут применяться и для реализации более сложных схем чтения/записи. Для этого имеется все необходимое: перегрузки конструкторов, методы последовательного доступа, методы сдвига указателя. Но мы их рассматривать не будем, поскольку наша основная задача - выполнить обзор потоков как можно шире, а впереди еще много вкусного.