oldi

Знакомство с Microsoft .NET Framework
Часть 6. Потоки

Алексей Федоров

Пространство имен System.IO

Потоки

   Класс BufferedStream

   Класс FileStream

   Класс MemoryStream

   Класс NetworkStream

   Класс CryptoStream

   Чтение и запись потоков

   Класс BinaryReader

   Класс BinaryWriter

   Класс StreamReader

   Класс StreamWriter

   Класс StringWriter

   Другие классы на базе класса 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