Руководство CSharp Streams - двоичные потоки в C#

1- Обзор Stream

Stream - класс, который имитирующий поток byte (байтов), выстроенные в ряд. Например, передача данных в сети, передаваемые данные - это непрерывный поток байтов от первого байта до последнего байта.
Stream - это базовый класс, другие потоки (stream) расширенные от этого класса. В C# существует несколько классов, которые расширены из класса Stream для различных целей, таких как:
Class Описание
BufferedStream Утилитарный поток, обернутый (wrap) другим потоком, который помогает повысить эффективность.
FileStream Поток использующийся для чтения записи данных в файл.
MemoryStream Поток работающий с данными в памяти.
UnmanagedMemoryStream
IsolatedStorageFileStream
PipeStream
NetworkStream
CryptoStream Поток читает записывает зашифрованные данные.
DeflateStream
GZipStream
Stream является абстрактным классом, он не может инициализировать объект сам, вы можете инициализировать объект Stream из конструкторов подкласса. Класс Stream предоставляет базовые методы работы с потоками данных, а именно метод чтения / записи байта или массив байтов.
В зависимости от потока есть поток, который поддерживает как чтение, так и запись, а также seek (поиск) путем перемещения курсора в потоке, читает и записывает данные в позиции курсора.
Свойства (property)  Stream:
Свойства Описание
CanRead Свойство дает знать, поддерживает ли текущий поток чтение.
CanSeek Свойство дает знать, поддерживает ли текущий поток поиск.
CanWrite Свойство дает знать, поддерживает ли текущий поток запись.
Length Возвращает длину потока в байтах.
Position Текущая позиция курсора в потоке.
Методы Stream:

2- Базовй пример Stream

С  Stream ​​​​​​​в ы можете записать в поток каждый байт или массив байтов. И при чтении вы можете читать каждый байт или несколько байтов и прикрепленные к  временному массиву.
Один byte равен 8 bit, в котором bit равен 0 или 1. Таким образом, 1 byte соответствует числу от 0 до 255 (2 ^ 8-1).

2.1- Пример записи данных потока

А теперь давайте начнем с простого примера, создайте Stream, который записывает данные в файл. Вы можете записать каждый байт в поток или записать массив байтов в поток.
StreamWriteDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CSharpStreamsTutorial
{
    class StreamWriteDemo
    {
        public static void Main(string[] args)
        {
            string path = @"C:\temp\MyTest.txt"; 
  
            // Создать родительскую папку.
            Directory.CreateDirectory(@"C:\temp");

            // Создать объект Stream через constructor в FileStream.
            // FileMode.Create: Создать новый файл для записи, если файл уже существует, 
            // то переопределить этот файл.
            Stream writingStream = new FileStream(path, FileMode.Create);

            try
            {
                // Массив байтов (1byte < 2^8).
                // Этот массив соответствует: {'H','e','l','l','o',' ','W','o','r','l','d'}.
                byte[] bytes = new byte[] { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100 };

                if (writingStream.CanWrite)
                {
                    writingStream.Write(bytes, 0, bytes.Length); 
                  
                    // Написать еще один байт (33 = '!')
                    writingStream.WriteByte(33);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Error:" + e);
            }
            finally
            {
                // Закрыть Stream, освободитьт ресурс.
                writingStream.Close();
            }
            Console.ReadLine();

        }

    }
}
Запуск примера:
Примечание: В таблице кодировке символов ASCII каждый символ CSII соответствует числу  < 256.
Character Value   Character Value
H 72   W 87
e 101   r 114
l 108   d 100
o 111     32
! 33      
Вы можете изучить больше про кодовую таблицу  ASCII по ссылке:

2.2- Пример чтения данных потока

В примере выше вы должны записать данные в файл C:\temp\MyTest.txt, теперь вы можете написать поток для чтения данных из этого файла.
StreamReadDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CSharpStreamsTutorial
{
    class StreamReadDemo
    {
        public static void Main(string[] args)
        {
            String path = @"C:\temp\MyTest.txt";

            if (!File.Exists(path))
            {
                Console.WriteLine("File " + path + " does not exists!");
                return;
            }

            // Создать объект Stream через Constructor класса FileStream.
            // FileMode.Open: Открыть файл для чтения.
            using (Stream readingStream = new FileStream(path, FileMode.Open))
            {
                byte[] temp = new byte[10];
                UTF8Encoding encoding = new UTF8Encoding(true);

                int len = 0;

                // Прочитать элементы в Stream и прикрепить к элементам массива 'temp'.
                // (Прикрепить к позициям начиная с  0, 
                //   каждый раз максимально читает 'temp.Length' элементы)
                // Одновременно возвращает количество прочитанных байтов.
                while ((len = readingStream.Read(temp, 0, temp.Length)) > 0)
                {
                    // Конвертировать в строку (String).
                    // ('len' элемент, начинающийся с индекса 0).
                    String s = encoding.GetString(temp, 0, len);
                    Console.WriteLine(s);
                }
            }

            Console.ReadLine();
        }
    }

}
Запуск примера:

3- FileStream

Filestream - это класс, который расширен из класса Stream, FileStream используется для чтения и записи данных в файл, он наследует свойства (property), методы Stream и одновременно имеет отдельные функции для чтения и записи данных в файл.
Есть несколько режимов чтения-записи данных в файл:
FileMode Описание
Append Открыть файл если он уже существует, перемещает сурсор в конец файла для продолжения записи в файл, если не существует, он будет создан.
Create Говорит операционной системе создать новый файл. Если папка существует, то будет переопределена.
CreateNew Говорит операционной система создать новый файл. Если файл уже существует, выбросится исключение IOException. Этот мод требует авторизацию FileIOPermissionAccess.Write
Open Говорит операционной системе открыть существующий файл.  Если файл не существует, выбросится исключениеSystem.IO.FileNotFoundException
OpenOrCreate Говорит операционной системе открыть файл если он существует; если нет, то создается новый файл.
Truncate Говорит операционной системе открыть существующий файл. Когда откроется файл, он будет отрезан так, что содержание возвращается к 0 byte.
​​​​​​​Пример с  FileMode:
FileStreamFileModeDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CSharpStreamsTutorial
{
    class FileStreamFileModeDemo
    {
        public static void Main(string[] args)
        {
            String path = @"C:\temp\MyTest.txt";

            if (!File.Exists(path))
            {
                Console.WriteLine("File " + path + " does not exists!");

                // Удостовериться, что родительская папка существует.
                Directory.CreateDirectory(@"C:\temp");
            }

            // Создать FileStream для записи данных.
            // (FileMode.Append: Открыть файл, чтобы записать дальше в конце файла,
            // если файл не существует, то создать новый).
            using (FileStream writeFileStream = new FileStream(path, FileMode.Append) )
            {
                string s = "\nHello every body!";

                // Конвертировать строку в массив байтов по кодированию UTF8.
                byte[] bytes = Encoding.UTF8.GetBytes(s);

                // Записать байты в файл.
                writeFileStream.Write(bytes, 0, bytes.Length); 
            }
            Console.WriteLine("Finish!");

            Console.ReadLine();
        }
    
    }
}
Запуск примера:
С FileMode.Append данные будут добавлены в файл, если файл уже существует:

Constructor:

В классе FileStream имеется 11 constructor (за исключением устаревших constructor), используемых для инициализации объекта FileStream:
FileStream Constructors
FileStream(SafeFileHandle, FileAccess)     

FileStream(SafeFileHandle, FileAccess, Int32)

FileStream(SafeFileHandle, FileAccess, Int32, Boolean)     

FileStream(String, FileMode)

FileStream(String, FileMode, FileAccess)

FileStream(String, FileMode, FileAccess, FileShare)

FileStream(String, FileMode, FileAccess, FileShare, Int32)

FileStream(String, FileMode, FileAccess, FileShare, Int32, Boolean)

FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions)     

FileStream(String, FileMode, FileSystemRights, FileShare, Int32, FileOptions)

FileStream(String, FileMode, FileSystemRights, FileShare, Int32, FileOptions, FileSecurity)
Однако у вас также есть другие способы создания объекта FileStream, например, через FileInfo, это класс, который представляет файл в системе.
Методы FileInfo возвращающие FileStream. Описание
Create() По умолчанию, весь доступ к чтению записи нового файла будет прикреплен ко всем пользователям.
Open(FileMode)    Открыть файл в указанном режиме.
Open(FileMode, FileAccess)    Открыть файл в указанном режиме чтения записи, или авторизацией чтения записи.
Open(FileMode, FileAccess, FileShare) Открыть файл в указанном режиме чтения записи, или авторизацией чтения записи, и опциями разделения.
OpenWrite() Создать FileStream только для записи данных.
OpenRead() Создать FileStream только для чтения данных.
Смотрите так же:
Cоздать Filestream через FileInfo:
FileStreamFileInfoDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CSharpStreamsTutorial
{
    class FileStreamFileInfoDemo
    {

        public static void Main(string[] args)
        {
            FileInfo afile = new FileInfo(@"C:\temp\MyTest.txt");

            if (afile.Exists)
            {
                Console.WriteLine("File does not exist!");
                Console.Read();
                return;
            }
            // Open file and truncate all bytes.
            using (FileStream stream = afile.Open(FileMode.Truncate))
            {
                String s = "New text";

                byte[] bytes = Encoding.UTF8.GetBytes(s);

                stream.Write(bytes, 0, bytes.Length);
            }

            Console.WriteLine("Finished!");
            Console.Read();

        }
    }

}

4- BufferedStream

BufferedStream - это класс, который расширен из класса Stream, это поток (stream), который обертывает (wrap) другой поток и помогает повысить эффективность чтения и записи данных.
BuffedStream имеет только двa конструктора (constructor), он обертывает другой поток (stream).
Конструктор (Constructor) Описание
BufferedStream(Stream) Инициализирует объект BufferedStream с размером буфера по умолчанию 4096 байт.
BufferedStream(Stream, Int32) Инициализирует объект​​​​​​​ BufferedStream с указанным размером буфера.
Я создаю ситуацию, вы создаете BufferedStream обертывающий FileStream для записи данных в файл. Данные, записанные в буфер потока, будут временно размещены в памяти, а когда буфер будет заполнен, данные автоматически сбрасываются (Flush) в файл, вы можете сами сбросить данные в файл с помощью метода Flush(). Использование BufferedStream в этом случае уменьшает частоту необходимости записывать на диск, и, следовательно, повышает эффективность программы.
Примером ниже является  BufferedStream обертывающий поток записи данных в файл:
BufferedStreamWriteFileDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CSharpStreamsTutorial
{
    class BufferedStreamWriteFileDemo
    {
        public static void Main(string[] args)
        {
            String fileName = @"C:\temp\MyFile.txt";

            FileInfo file = new FileInfo(fileName);

            // Удостовериться, что папка существует.
            file.Directory.Create();

            // Создать новый файл, если он уже существует то будет переопределен.
            // Возвращает объект FileStream.
            using (FileStream fileStream = file.Create())
            {
                // Создать объект BufferedStream обертывающий FileStream.
                // (Указать буфер (buffer) с емкостью10000 bytes).
                using (BufferedStream bs = new BufferedStream(fileStream, 10000))
                {
                    int index = 0;
                    for (index = 1; index < 2000; index++)
                    {
                        String s = "This is line " + index + "\n";

                        byte[] bytes = Encoding.UTF8.GetBytes(s);

                        // Записать в буфер (buffer),
                        // когда буфер заполнится, он автоматически сдвигает данные в файл.
                        bs.Write(bytes, 0, bytes.Length);
                    }

                    // Сдвинуть остальные данные в буфере в файл.
                    bs.Flush();
                } 
                
            }

            Console.WriteLine("Finished!");
            Console.Read();
        }
    }

}
Результат запуска примера:

5- MemoryStream

Класс MemoryStream непосредственно расширен из класса Stream, это поток (stream), данные которого хранятся (store) в памяти.
По сути, MemoryStream - это объект, который управляет буфером (buffer), - это массив байтов, когда байты записываются в этот поток, они автоматически будет прикреплены к следующей позиции от текущей позиции курсора в массиве. Когда буфер заполнен, создается новый массив большего размера и копируются данные из старого массива.

Constructor:

MemoryStream()   
MemoryStream(Byte[] buffer)
MemoryStream(Byte[] buffer, Boolean writable)
MemoryStream(Byte[] buffer, Int32 index, Int32 count, Boolean writable) 
MemoryStream(Byte[] buffer, Int32 index, Int32 count, Boolean, Boolean publiclyVisible)  
MemoryStream(Byte[], Int32, Int32, Boolean, Boolean)  
 MemoryStream(Int32 capacity)
Пример:
MemoryStreamDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CSharpStreamsTutorial
{
    class MemoryStreamDemo
    {
        static void Main()
        {
            // Создать объект MemoryStream с емкостью 100 bytes.
            MemoryStream memoryStream = new MemoryStream(100);

            byte[] javaBytes = Encoding.UTF8.GetBytes("Java");
            byte[] csharpBytes = Encoding.UTF8.GetBytes("CSharp");

            // Записать байты в memoryStream (поток памяти).
            memoryStream.Write(javaBytes, 0, javaBytes.Length);
            memoryStream.Write(csharpBytes, 0, csharpBytes.Length);

            // Записать емкость и длину Stream.
            // ==> Capacity: 100, Length: 10.
            Console.WriteLine("Capacity: {0} , Length: {1}",
                                  memoryStream.Capacity.ToString(),
                                  memoryStream.Length.ToString());

            // В данный момент позиция курсора (cursor) стоит позади символа 'p'.
            // ==> 10.
            Console.WriteLine("Position: "+ memoryStream.Position);

            // Передвинуть курсор назад на 6 byte, по сравнению с текущей позицией.
            memoryStream.Seek(-6, SeekOrigin.Current);

            // В данный момент позиция курсора стоит позади символа 'a' и впереди 'C'.
            // ==> 4.
            Console.WriteLine("Position: " + memoryStream.Position);

            byte[] vsBytes = Encoding.UTF8.GetBytes(" vs ");

            // Записать данные в memoryStream (поток памяти).
            memoryStream.Write(vsBytes, 0, vsBytes.Length);


            byte[] allBytes = memoryStream.GetBuffer();

            string data = Encoding.UTF8.GetString(allBytes);

            // ==> Java vs rp
            Console.WriteLine(data);

            Console.WriteLine("Finish!");
            Console.Read();

             
        }
    }

}
Запуск примера:

6- UnmanagedMemoryStream

UnmanagedMemoryStream позволяет вам читать поток данных, который не управляется, без необходимости копирования всего на управление памятью Heap перед использованием. Это помогает вам экономить память, если вам приходится иметь дело с большим количеством данных.

Обратите внимание, что для MemoryStream существует ограничение в 2 ГБ, поэтому вы должны использовать UnmanagedMemoryStream, если превысили этот предел.
Я исхожу из ситуации: в памяти есть дискретные данные. И вы можете их собрать, чтобы управлять с помощью  UnmanagedMemoryStream, управляя указателями (pointer) вышеупомянутых дискретных данных, вместо того, чтобы копировать их в поток (stream) для управления.

Constructor:

UnmanagedMemoryStream()
UnmanagedMemoryStream(Byte* pointer, Int64 length)
UnmanagedMemoryStream(Byte* pointer, Int64 length, Int64 capacity, FileAccess access)
UnmanagedMemoryStream(SafeBuffer buffer, Int64 offset, Int64 length)  
UnmanagedMemoryStream(SafeBuffer buffer, Int64 offset, Int64 length, FileAccess access)
  • TODO

7- CryptoStream

CryptoStream - класс, используемый для шифрования потока данных.
На приведенном ниже изображении показано, как CryptoStream обертывает другой поток (например, создает поток записи файла), когда вы записываете байтовые данные в CryptoStream, эти байты (bytes) будут зашифрованы в другие байты перед сбросом в поток, который записывается в файл. Теперь содержимое файла было зашифровано.

Обратите внимание, что вы можете выбрать алгоритм шифрования при создании объекта CryptoStream.
В обратной ситуации, поток  CryptoStream обертывает поток чтения файла (файл, содержимое которого было зашифровано выше), байты в  потоке FileStream были зашифрованы (encrypted), и будут дешифрован (decrypt) используя  CryptoStream.
Еще одна важная вещь, которую вы должны запомнить, заключается в том, что не все алгоритмы шифрования имеют двусторонние способы шифрования и дешифрования.
Давайте рассмотрим пример:

Здесь я использую алгоритм DES для шифрования и дешифрования, вам необходимо предоставить 128-битный массив, который является ключом вашей безопасности.
DES Algorithm
// Объект предоставляет кодированный алгоритм DES.
DESCryptoServiceProvider provider = new DESCryptoServiceProvider();

// Использовать ваш приватный ключ (private key) (Должна быть одна строка 128bit = 8byte).
// (Соответствует 8 символам ASCII)
provider.Key = ASCIIEncoding.ASCII.GetBytes("1234abcd");
provider.IV = ASCIIEncoding.ASCII.GetBytes("12345678");

// Энкриптор (Encryptor).
ICryptoTransform encryptor = provider.CreateEncryptor();

// Декриптор (Decrytor).
ICryptoTransform decryptor = provider.CreateDecryptor();
Смотрите полный пример.
CryptoStreamExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;

namespace CSharpStreamsTutorial
{
    class CryptoStreamExample
    {
        public static void Main(string[] args) 
        {
            // Предоставляет алгоритм кодирования DES.
            DESCryptoServiceProvider provider = new DESCryptoServiceProvider();

            // Кодовый ключ (secret key) (Должна быть строка 128bit = 8byte).
            // (Соответствует 8 символам ASCII)
            provider.Key = ASCIIEncoding.ASCII.GetBytes("1234abcd");
            provider.IV = ASCIIEncoding.ASCII.GetBytes("12345678");


            String encryedFile = @"C:\temp\EncryptedFile.txt";

            // Поток для записи файла.
            using (FileStream stream = new FileStream(encryedFile, FileMode.OpenOrCreate, FileAccess.Write))
            { 
                // Энкриптор (Encryptor).
                ICryptoTransform encryptor = provider.CreateEncryptor();

                // Создать CryptoStream обертывающий FileStream.
                // (FileStream в данном случае используется для записи данных в файл).
                using (CryptoStream cryptoStream = new CryptoStream(stream,
                                     encryptor, CryptoStreamMode.Write))
                {
                    // Массив некодированных байтов.
                    byte[] data = ASCIIEncoding.ASCII.GetBytes("Bear, I love you. OK?");

                    // Записать в cryptoStream.
                    cryptoStream.Write(data, 0, data.Length);
                } 

            }
            Console.WriteLine("Write to file: " + encryedFile);

            // Далее прочитать кодированный файл, только что созданный в шаге выше.
            using (FileStream stream = new FileStream(encryedFile, FileMode.Open, FileAccess.Read))
            {
                // Декриптор (Decryptor).
                ICryptoTransform decryptor = provider.CreateDecryptor();

                // Создать CryptoStream обертывающий FileStream.
                // (FileStream в данном случае используется для чтения файла).
                using (CryptoStream cryptoStream = new CryptoStream(stream,
                                     decryptor, CryptoStreamMode.Read))
                {
                    byte[] temp = new byte[1024];
                    int read=0;
                    while((read =cryptoStream.Read(temp,0,temp.Length )) >0 )
                    {
                        String s= Encoding.UTF8.GetString(temp,0,read);

                        Console.Write(s);
                    } 
                } 
            }

            // Finished
            Console.Read();
        }
    }

}
Запуск примера:
Просмотрите содержимое созданного файла.