Знакомство с Microsoft .NET Framework
Часть 6. Потоки
Другие классы на базе класса TextWriter
Мы продолжаем разговор о Microsoft .NET Framework (см. КомпьютерПресс № 11-12’2001, 1-4’2002) и библиотеке классов .NET Framework Class Library. В этом номере мы рассмотрим пространство имен System.IO и классы, связанные с потоковым вводом-выводом.
этой части нашего обзора библиотеки классов .NET Framework Class Library мы начнем знакомство с пространством имен System.IO и рассмотрим потоки, средства для их чтения и записи из них. В следующем номере мы расскажем также о входящих в пространство имен System.IO классах, предназначенных для работы с файловой системой.
Пространство имен System.IO
то пространство имен содержит классы и другие типы данных, используемые для синхронной и асинхронной работы (чтение и запись) с потоками и файлами. Пространство имен System.IO включает классы, реализующие потоки, средства чтения и записи потоков, а также классы для работы с файловой системой. Основные классы, входящие в пространство имен System.IO, показаны на рис. 1.
Потоки
ласс Stream — это абстрактное представление последовательности байтов. Классы, наследующие от класса Stream, обеспечивают более специфические функции — работу с файлами (класс FileStream), памятью (класс MemoryStream) и сетью (класс NetworkStream). В целом потоки поддерживают операции чтения, записи, а также операции позиционирования. Отметим, что операция позиционирования требует определения текущей позиции и может не поддерживаться в потоках определенного типа, например в сетевых потоках. Для того чтобы проверить возможности того или иного типа потока, следует использовать свойства CanRead, CanWrite и CanSeek.
Свойство Length используется для нахождения длины потока — числа байт, содержащихся в потоке (значение типа Long). Для определения текущей позиции в потоке или для ее изменения используется свойство Position (значение типа Long).
Класс Stream содержит несколько методов, определяющих базовую функциональность всех потоков:
- BeginRead(Byte(), Integer, Integer, AsyncCallback, Object), ReadByte(), Read(Byte(), Integer, Integer) и EndRead(IAsyncResult) — эти методы используются для начала и завершения асинхронной операции чтения, для чтения байта или последовательности байтов из текущей позиции в потоке;
- BeginWrite(Byte(), Integer, Integer, AsyncCallback, Object), WriteByte(Byte), Write(Byte(), Integer, Integer) и EndWrite(IAsyncResult) — эти методы используются для начала и завершения асинхронной операции записи, записи байта или последовательности байтов в текущую позицию в потоке;
- Seek(Long, SeekOrigin) — этот метод служит для изменения текущей позиции в потоке;
- SetLength — данный метод изменяет длину текущего потока;
- Flush() — этот метод заставляет записать в поток все буферизованные данные;
- Close() — этот метод закрывает текущий поток.
В следующей таблице показано, в каких случаях мы не можем использовать некоторые свойства и методы классов, унаследованных от класса Stream.
Как мы отмечали выше, класс Stream служит в качестве базового класса для нескольких типов потоков. На рис. 2 показаны классы, унаследованные от абстрактного класса Stream.
Отметим, что классы NetworkStream и CryptoStream реализованы в других пространствах имен — в пространстве имен System.Net.Sockets и System.Security. Cryptography соответственно.
Класс BufferedStream
Класс BufferedStream используется для буферизованных потоковых операций чтения и записи (но не той и другой одновременно) в другой поток. Этот класс создает буфер в памяти (по умолчанию размер буфера равен 4 Кбайт, но это значение может быть изменено с помощью перегруженного конструктора), который используется для кэширования данных. В результате повышается производительность операций чтения и записи — за счет сокращения числа обращений к функциям операционной системы.
С целью создания буфера для существующего потока используется конструктор класса BufferedStream, а в качестве входного параметра указывается существующий поток. Дальнейшие операции выполняются через методы класса BufferedStream, а все изменения в буфере сохраняются в потоке вызовом метода Flush.
Класс FileStream
Класс FileStream используется для чтения и записи файлов. Мы можем использовать этот класс для чтения и записи байтов, символов, строк и других типов данных. Класс FileStream поддерживает синхронное и асинхронное открытие файлов, синхронные операции чтения и записи (методы Read и Write), а также асинхронные операции чтения и записи (методы BeginRead и BeginWrite). Асинхронные операции завершаются вызовом методов EndRead и EndWrite соответственно. Режим по умолчанию — синхронный; для проверки режима мы используем свойство IsAsync. Для асинхронных операций необходим объект WaitHandle. Метод Seek используется для произвольного доступа к файлам. Свойство Position позволяет нам узнать или установить текущую позицию в потоке. Методы Lock и Unlock служат для предотвращения доступа ко всему файлу или к его части, а также для отмены ранее установленного запрета доступа. Свойство Length возвращает длину потока в байтах, а метод SetLength служит для задания длины потока. Методы ReadByte и WriteByte используются для чтения и записи одного байта. Для других примитивных типов нам необходимы классы BinaryReader и BinaryWriter соответственно.
От класса FileStream наследует класс IsolatedStorageFileSystem (пространство имен System.IO.IsolatedStorage), служащий для чтения, записи и создания файлов в изолированном хранилище. Изолированное хранилище предоставляет в наше распоряжение виртуальную файловую систему, позволяющую читать и записывать данные, недоступные извне. Изолированное хранилище обеспечивает изоляцию данных на уровне пользователя, сборки или домена приложения.
Класс MemoryStream
Класс MemoryStream может использоваться для создания потока, содержимое которого хранится не на диске и не в сети, а в памяти. Этот класс задействует байтовый массив, который может иметь либо фиксированную, либо произвольную длину. В последнем случае мы можем изменять размер массива, читать из него и записывать в него. Что касается потока с фиксированной длиной, то в него мы можем только записывать.
Для того чтобы выяснить число байтов, выделенных под поток в памяти, мы пользуемся свойством Capacity, возвращающим данные типа Integer. Свойство Length возвращает реальное число байтов в потоке (значение типа Long), а метод GetBuffer() возвращает массив байтов, в котором располагается поток. Для сохранения всего содержимого потока в байтовом массиве используется метод ToArray(). Метод WriteTo(Stream) служит для копирования всего потока в другой поток.
Следующий пример показывает, как использовать класс MemoryStream для создания нового потока в памяти, задания его содержимого и сохранения этого потока в файле:
‘———————————————————— ‘ Использование класса MemoryStream ‘———————————————————— Imports System Imports System.IO Module VBDemo Sub Main() Dim Bytes() As Byte = New Byte(10) {} Dim I As Integer Dim MemStr As New MemoryStream() Dim FileStr As New FileStream(“c:\temp\bytes.bin”, _ FileMode.CreateNew) Dim Rand As System.Random = New System.Random() For I = 0 To 9 Bytes(I) = Rand.Next(0, 100) Next MemStr.Write(Bytes, 0, I) MemStr.WriteTo(FileStr) MemStr.Close() FileStr.Close() End Sub End Module
Ниже мы кратко рассмотрим еще два типа потоков, которые наследуют от класса Stream, но реализованы вне пространства имен System.IO.
Класс NetworkStream
Этот класс служит для посылки данных по сети. Класс NetworkStream реализует поток, не поддерживающий операцию позиционирования. Поэтому мы не можем использовать свойство Position и метод Seek при работе с потоком этого типа.
Более подробно сетевые функции в Microsoft .NET мы рассмотрим в одной из следующих частей данной статьи.
Класс CryptoStream
Этот класс реализует поток, который связывает потоки данных с криптографическими трансформациями. Класс CryptoStream реализован в пространстве имен System.Secutity.Cryptography.
Для шифрования и расшифровки потоков мы должны выбрать провайдеров, которые обеспечивают шифровку и расшифровку. В настоящее время мы можем выбирать между симметричными алгоритмами типа Encryption Standard (DES), RC2, Triple Data Encryption Standard (TripleDES) и Rijndael/AES, асимметричными алгоритмами (которые также известны как публичные ключи) типа RSA, DSA и хэш-алгоритмами типа MD5, SHA1, SHA256, SHA384 и SHA512. Пространство имен System.Security.Cryptography.X509 также содержит минимальную поддержку публичных сертификатов.
Классы, которые реализуют поддержку цифровой подписи в XML-документах, располагаются в пространстве имен System.Security.Cryptography.XML.
Большинство алгоритмов в пространстве имен System.Secutity.Cryptography реализовано на основе интерфейса Microsoft CryptoAPI. Некоторые алгоритмы шифрования — SHA256, SHA384, SHA512 и Rijndael/AES — в настоящее время не поддерживаются интерфейсом CryptoAPI.
После того как мы выберем провайдера, который обеспечивает нам шифровку и расшифровку: DESCryptoServiceProvider, RC2CryptoServiceProvider, TripleDESCryptoServiceProvider и т.п., мы будем использовать его с ключом и инициализационным вектором для зашифровки или расшифровки указанного потока.
Последний шаг — сохранение зашифрованного или расшифрованного потока в файле. В следующем примере показано, как зашифровать массив байтов, сохранить его в файле, а затем расшифровать на основе алгоритма DES. Мы используем здесь конструктор по умолчанию, который заполняет необходимые параметры выбранного нами алгоритма:
'---------------------------------------- ' Использование класса CryptoStream '---------------------------------------- Imports System Imports System.IO Imports System.Security.Cryptography Module VBDemo Sub Main() Dim Bytes() As Byte = {65, 66, 67, 68, 69, 70, 71, 72, 73, 74} Dim EncBytes() As Byte = New Byte(15) {} Dim DecBytes() As Byte = New Byte(10) {} Dim FileName As String = "c:\temp\text.enc" Dim EncFile As New FileStream(FileName, FileMode.Create, _ FileAccess.Write) Dim DES As New DESCryptoServiceProvider() Dim DESEncrypt As ICryptoTransform = DES.CreateEncryptor() Dim CryptoStreamEnc As New CryptoStream(EncFile, DESEncrypt, _ CryptoStreamMode.Write) Console.WriteLine("Original Data") ToHexArray(Bytes) CryptoStreamEnc.Write(Bytes, 0, Bytes.Length) CryptoStreamEnc.Close() EncFile.Close() EncFile = New FileStream(FileName, FileMode.Open, FileAccess.Read) EncFile.Read(EncBytes, 0, EncFile.Length) EncFile.Close() Console.WriteLine("Encrypted Data") ToHexArray(EncBytes) Console.WriteLine() Dim DecFile As New FileStream(FileName, FileMode.Open, _ FileAccess.Read) Dim DESDecrypt As ICryptoTransform = DES.CreateDecryptor() Dim CryptoStreamDec As New CryptoStream(DecFile, DESDecrypt, _ CryptoStreamMode.Read) Dim Reader As New BinaryReader(CryptoStreamDec) Console.WriteLine("Decrypted Data") DecBytes = Reader.ReadBytes(10) ToHexArray(DecBytes) End Sub Sub ToHexArray(ByVal A As Byte()) Dim I As Integer For I = 0 To A.GetUpperBound(0) Console.Write("0x{0:x2} ", A(I)) If I = 7 Then Console.WriteLine() End If Next End Sub End Module
Использование фиксированной длины ключа поддерживается во всех алгоритмах шифрования, но для алгоритмов, реализованных на основе CryptoAPI, мы должны установить High Encryption Pack, входящий в состав Windows 2000 Service Pack, Windows NT 4.0 Service Pack 6a или Internet Explorer 5.5 для Windows Me, Windows 98 и Windows 95.
Чтение и запись потоков
Пространство имен System.IO содержит четыре пары классов для чтения и записи потоков: классы, используемые для чтения и записи последовательной серии байтов или символов. К этим классам относятся классы BinaryReader и BinaryWriter для работы с примитивными типами данных, определенными в Common Type System, а также классы для чтения и записи последовательностей символов — TextReader и TextWriter, строк — StringReader и StringWriter и потоков — StreamReader и StreamWriter.
Класс BinaryReader
Класс BinaryReader предоставляет нам механизмы для чтения примитивных типов как бинарных значений. Он содержит методы для чтения каждого типа, определенного в Common Type System — ReadBoolean, ReadByte (ReadBytes), ReadChar (ReadChars), ReadDecimal, ReadDouble и т.д.
Для получения потока, ассоциированного с BinaryReader, что неизменно происходит в конструкторе, мы можем использовать свойство BaseStream типа Stream. Метод PeekChar() возвращает следующий символ из потока, но не перемещает указатель текущей позиции — это означает, что мы можем использовать данный метод для проверки того, имеются ли в потоке еще символы, перед тем как прочитать их. Метод Read(Byte(), Integer, Integer) or Read(Char(), Integer, Integer) используется для чтения указанного числа символов или байтов из потока. Этот метод может применяться вместо методов для чтения примитивных типов данных.
Следующий пример показывает, как использовать класс BinaryReader для создания простой утилиты шестнадцатеричного дампа:
‘———————————————————— ‘ Использование класса BinaryReader ‘———————————————————— Imports System Imports System.IO Module VBDemo Sub Main() Dim Bytes As Byte() Dim I As Integer Dim Reader As BinaryReader Reader = New BinaryReader(File.OpenRead (“c:\demo.exe”)) While Reader.PeekChar() > -1 Bytes = Reader.ReadBytes(16) For I = 0 To Bytes.GetUpperBound(0) Console.Write(“0x{0:X2}|”, Bytes(I)) Next Console.WriteLine() End While End Sub End Module
Класс BinaryWriter
Мы используем класс BinaryWriter для записи данных примитивных типов в ассоциированный поток. Этот класс содержит метод Write(), у которого существуют перегруженные методы для записи значений различных типов в текущий поток.
Объединяя методы классов BinaryReader и BinaryWriter, мы можем написать код, который выполняет операции копирования типа той, что приведена в следующем примере:
‘———————————————————— ‘ Использование класса BinaryWriter ‘———————————————————— Imports System Imports System.IO Module VBDemo Sub Main() Dim Bytes As Byte() Dim Reader As BinaryReader Dim Writer As BinaryWriter Reader = New BinaryReader(File.OpenRead (“c:\demo.exe”)) Writer = New BinaryWriter(File.Create (“c:\demo_copy.exe”)) While Reader.PeekChar() > -1 Bytes = Reader.ReadBytes(1024) Writer.Write(Bytes) End While Reader.Close() Writer.Flush() Writer.Close() End Sub End Module Класс TextReader
Класс TextReader используется для чтения последовательности символов из ассоциированного потока. Этот класс служит основой для двух других классов: StreamReader и StringReader (рис. 3), которые мы рассмотрим ниже.
Класс TextReader содержит следующие методы, которые мы можем использовать в нашем коде:
- Peek() — этот метод возвращает следующий символ из потока, но не перемещает указатель текущей позиции;
- Read() или Read(Char(), Integer, Integer) — этот метод применяется для чтения указанного числа символов из потока;
- ReadBlock(Char(), Integer, Integer) — этот метод считывает указанное число символов в буфер;
- ReadLine() — этот метод считывает целую строку символов из потока;
- ReadToEnd() — этот метод считывает все символы с текущей позиции до конца потока как одну строку.
Поскольку класс TextReader является абстрактным классом, мы не можем задействовать его напрямую — мы должны использовать либо класс StreamReader, либо класс StringReader (их мы рассмотрим ниже).
Класс StreamReader
Класс StreamReader применяется для чтения последовательности символов из указанного файла или другого потока. В следующих примерах показано, как использовать класс StreamReader, чтобы прочитать текстовый файл построчно и как одну строку:
‘———————————————————— ‘ Использование класса StreamReader ‘———————————————————— Imports System Imports System.IO Module VBDemo Sub Main() Dim FileName As String = _ “C:\Program Files\Microsoft.NET\FrameworkSDK\ include\corsym.h” Dim Reader As TextReader Dim I As Int32 Reader = New StreamReader(FileName) While Reader.Peek() > -1 Console.WriteLine(Reader.ReadLine) I += 1 End While Console.WriteLine(“Read {0:G} lines”, I) Reader.Close() End Sub End Module
Если нас не интересует число строк, которые мы должны прочитать, и мы знаем, что объем файла не очень велик, мы можем использовать метод ReadToEnd, как это продемонстрировано в следующем примере:
‘———————————————————— ‘ Использование класса StreamReader ‘———————————————————— Imports System Imports System.IO Module VBDemo Sub Main() Dim FileName As String = _ “C:\Program Files\Microsoft.NET\FrameworkSDK\ include\corsym.h” Dim Reader As TextReader Dim I As Int32 Reader = New StreamReader(FileName) Console.WriteLine(Reader.ReadToEnd) Reader.Close() End Sub End Module Класс StringReader
Класс StringReader служит для чтения символов из строк. В следующем примере показана возможность использования классов StreamReader и StringReader для чтения строки из текстового файла и чтения символов из этой строки как из потока:
‘———————————————————— ‘ Использование класса StringReader ‘———————————————————— Imports System Imports System.IO Module VBDemo Sub Main() Dim FileName As String = _ “C:\Program Files\Microsoft.NET\FrameworkSDK\ include\corsym.h” Dim Reader As TextReader Dim S As String Reader = New StreamReader(FileName) S = Reader.ReadLine Dim SReader As New StringReader(S) While SReader.Peek > -1 Console.WriteLine(Chr(SReader.Read)) End While Reader.Close() End Sub End Module Класс TextWriter
Класс TextWriter служит для записи последовательности символов в поток. Для байтовых операций записи мы должны использовать класс Stream, а для бинарной записи примитивных типов — класс BinaryWriter.
Класс TextWriter — это абстрактный класс, он служит в качестве базы для нескольких классов: классов StreamWriter, StringWriter, реализованных в пространстве имен System.IO, класса IntendedTextWriter из пространства имен System.CodeDom.Complier, а также классов HTTPWriter и HTMLWriter, реализованных в пространствах имен System.Web и System.Web.UI соответственно (рис. 4).
Класс StreamWriter
Класс StreamWriter применяется для вывода последовательности символов в той или иной кодировке. По умолчанию используется экземпляр класса UTF8Encoding для записи символов в кодировке Unicode UTF-8. Существует несколько перегруженных конструкторов, позволяющих указать поток, кодировку по умолчанию, задать кодировку, размер буфера и тип операции: требуется перезаписать существующий файл или информация должна быть к нему добавлена.
Свойство AutoFlush служит для указания, следует ли записывать содержимое буфера в поток после каждого вызова методов Write и WriteLine.
Класс StringWriter
Класс StringWriter используется для записи строки, которая хранится в классе StringBuilder, реализованном в пространстве имен System.Text. Более подробно об этом сказано в части 5 данной статьи (см. КомпьютерПресс № 4’2002).
Другие классы на базе класса TextWriter
Как мы отмечали выше, в библиотеке классов .NET Framework Class Library существует несколько дополнительных классов, базирующихся на абстрактном классе TextWriter:
- IndentedTextWriter (пространство имен System.CodeDom.Complier) — этот класс служит для записи строк с выравниванием по левому краю на основе заданного шага табуляции;
- HTTPWriter (пространство имен System.Web) — этот класс доступен через объект HTTPRequest — его метод Write вызывает объект HTTPWriter;
- HTMLTextWriter (пространство имен System.Web.UI) — этот класс используется серверными компонентами ASP.NET для отображения HTML-содержимого на клиенте;
- HTML32TextWriter (пространство имен System.Web.UI) — этот класс расширяет класс HTMLTextWriter и обеспечивает поддержку отображения HTML версии 3.2.
На этом мы завершаем рассмотрение потоков и существующих в них средств чтения и записи. Тема следующей статьи данного цикла — классы из пространства имен System.IO, служащие для работы с файлами и каталогами. В июньском номере мы поговорим о классах FileSystemInfo, FileInfo, DirectoryInfo, File и Path.
КомпьютерПресс 5'2002