Hướng dẫn sử dụng Stream - luồng vào ra nhị phân trong C#
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Tổng quan về Stream

Stream là một class nó mô phỏng một dòng các byte được sắp hàng một cách liên tiếp nhau. Chẳng hạn như việc truyền tải dữ liệu trên mạng các dữ liệu truyền đi là dòng các byte liên tiếp nhau từ byte đầu tiên cho tới các byte cuối cùng.
Stream là một class cơ sở, các luồng stream khác mở rộng từ class này. Có một vài class đã được xây dựng sẵn trong C#, chúng mở rộng từ class Stream cho các mục đích khác nhau, chẳng han:
Class Mô tả
BufferedStream Một luồng tiện ích, nó bao bọc (wrap) một luồng khác giúp nâng cao hiệu năng luồng.
FileStream Luồng sử dụng để đọc ghi dữ liệu vào file.
MemoryStream Luồng làm việc với các dữ liệu trên bộ nhớ.
UnmanagedMemoryStream  
IsolatedStorageFileStream  
PipeStream  
NetworkStream  
CryptoStream Luồng đọc ghi dữ liệu được mật mã hóa.
DeflateStream  
GZipStream  
Stream là một class trìu tượng, tự nó không thể khởi tạo một đối tượng, bạn có thể khởi tạo một đối tượng Stream từ các cấu tử (Constructor) của class con. Class Stream cung cấp các phương thức cơ bản làm việc với luồng dữ liệu, cụ thể là các phương thức đọc ghi một byte hoặc một mảng các byte..
 
Tùy thuộc vào luồng, có những luồng hỗ trợ cả đọc và ghi, và cả tìm kiếm (seek) bằng cách di chuyển con trỏ trên luồng, và ghi đọc dữ liệu tại vị trí con trỏ.
Các thuộc tính của Stream:
Thuộc tính Mô tả
CanRead Thuộc tính cho biết luồng này có hỗ trợ đọc không.
CanSeek Thuộc tính cho biết luồng này có hỗ trợ tìm kiếm (seek) hay không
CanWrite Thuộc tính cho biết luồng này có hỗ trợ ghi hay không
Length Trả về độ dài của luồng (Số bytes)
Position Vị trí hiện tại của con trỏ trên luồng.
Các phương thức của Stream:

2- Ví dụ cơ bản Stream

Với Stream bạn có thể ghi từng byte hoặc ghi một mảng các byte vào luồng. Và khi đọc bạn có thể đọc từng byte hoặc đọc nhiều byte và gán vào một mảng tạm.
Một byte là 8 bit, trong đó một bit là 0 hoặc 1. Như vậy 1 byte tương ứng với một số từ 0 tới 255 (2^8 - 1).

2.1- Ví dụ luồng ghi

Và bây giờ hãy bắt đầu với một ví dụ đơn giản, tạo một Stream ghi dữ liệu vào File. Bạn có thể ghi từng byte vào stream hoặc ghi một mảng các byte vào 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";

           // Tạo thư mục cha.
           Directory.CreateDirectory(@"C:\temp");

         
           // Tạo một đối tương Stream từ class con FileStream.
           // FileMode.Create: Tạo file mới để ghi, nếu file đã tồn tại ghi đè file này.
           Stream writingStream = new FileStream(path, FileMode.Create);

           try
           {
               // Một mảng byte (1byte < 2^8).
               // Tương ứng với {'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);
                 
          
                   // Ghi thêm một byte (33 = '!')
                   writingStream.WriteByte(33);
               }
           }
           catch (Exception e)
           {
               Console.WriteLine("Error:" + e);
           }
           finally
           {
               // Đóng Stream, giải phóng tài nguyên.
               writingStream.Close();
           }
           Console.ReadLine();

       }

   }
}
Chạy ví dụ:
Chú ý: Trong bảng mã ký tự CSII, mỗi ký tự CSII tương ứng với một con số < 256.
Ký tự Giá trị   Ký tự Giá trị
H 72   W 87
e 101   r 114
l 108   d 100
o 111     32
! 33      
Bạn có thể tham khảo thêm về bảng mã ASCII tại:

2.2- Ví dụ luồng đọc

Ví dụ ở trên bạn đã ghi dữ liệu vào file C:\temp\MyTest.txt, bây giờ bạn có thể viết một Stream đọc dữ liệu từ file đó.
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;
           }

           // File đã được tạo ra, giờ tạo một Stream để đọc.
           // Tạo một đối tương Stream từ class con FileStream.
           // FileMode.Open: Mở file để đọc.
           using (Stream readingStream = new FileStream(path, FileMode.Open))
           {
               byte[] temp = new byte[10];
               UTF8Encoding encoding = new UTF8Encoding(true);

               int len = 0;

               // Đọc các phần tử trên luồng gán vào các phần tử của mảng temp.
               // (Gán vào các vị trí bắt đầu từ 0, mỗi lần đọc tối đa temp.Length phần tử)
               // Đồng thời trả về số byte đọc được.
               while ((len = readingStream.Read(temp, 0, temp.Length)) > 0)
               {
                   // Chuyển mảng temp chứa các byte vừa đọc được thành chuỗi.
                   // (Lấy 'len' phần tử bắt đầu từ vị trí 0).
                   String s = encoding.GetString(temp, 0, len);
                   Console.WriteLine(s);
               }
           }

           Console.ReadLine();
       }
   }

}
Chạy ví dụ:

3- FileStream

FileStream là một class mở rộng từ class Stream, FileStream được sử dụng để đọc và ghi dữ liệu vào file, nó được thừa kế các thuộc tính, phương thức từ Stream, đồng thời có thêm các chức năng dành riêng cho đọc ghi dữ liệu vào file.
Có một vài chế độ đọc ghi dữ liệu vào file:
FileMode Mô tả
Append Mở file nếu nó đã tồn tại, di chuyển con trỏ về cuối tập tin để nó thể ghi nối tiếp vào file, nếu file không tồn tại nó sẽ được tạo ra.
Create Nói với hệ điều hành tạo một tập tin mới. Nếu tập tin đã tồn tại, nó sẽ được ghi đè.
CreateNew Nói với hệ điều hành tạo ra một file mới. Nếu file đã tồn tại ngoại lệ IOException sẽ được ném ra. Chế độ này yêu cầu phải có quyền FileIOPermissionAccess.Write
Open Nói với hệ điều hành để mở một file đã tồn tại. Một ngoại lệ System.IO.FileNotFoundException sẽ được ném ra nếu file không tồn tại.
OpenOrCreate Nói với hệ điều hảnh nên mở một tập tin nếu nó tồn tại; nếu không, một tập tin mới sẽ được tạo ra.
Truncate Nói với hệ điều hàn nên mở tập tin khi nó tồn tại. Và khi file được mở, nó sẽ bị cắt hết nội dung trở về 0 byte.
Ví dụ với 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!");

               // Đảm bảo rằng thư mục chứa tồn tại.
               Directory.CreateDirectory(@"C:\temp");
           }

           // Tạo ra một FileStream để ghi dữ liệu.
           // (FileMode.Append: Mở file ra để ghi tiếp vào phía cuối của file,
           //  nếu file không tồn tại sẽ tạo mới).

           using (FileStream writeFileStream = new FileStream(path, FileMode.Append) )
           {
               string s = "\nHello every body!";

               // Chuyển một chuỗi thành mảng các byte theo mã hóa UTF8.
               byte[] bytes = Encoding.UTF8.GetBytes(s);

               // Ghi các byte xuống file.
               writeFileStream.Write(bytes, 0, bytes.Length);
           }
           Console.WriteLine("Finish!");

           Console.ReadLine();
       }
 
   }
}
Chạy ví dụ:
Với FileMode.Append dữ liệu sẽ được nối thêm vào file, nếu file đó đã tồn tại:

Cấu tử (Constructor):

Class FileStream có 11 constructor (Không tính các constructor bị lỗi thời) dùng để khởi tạo một đối tượng 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)
Tuy nhiên bạn cũng có các cách khác để tạo đối tượng FileStream, chẳng hạn thông qua FileInfo, đây là là class đại diện cho một file trong hệ thống.
Phương thức của FileInfo trả về FileStream. Mô tả
Create() Bởi mặc định, tất cả các quyền đọc ghi file mới này sẽ gán cho tất cả các users.
Open(FileMode)    Mở file với chế độ được chỉ định.
Open(FileMode, FileAccess)    Mở file với chỉ định chế độ đọc, ghi, hoặc quyền đọc ghi.
Open(FileMode, FileAccess, FileShare) Mở file với chỉ định chế độ đọc, ghi, hoặc quyền đọc ghi, và các lựa chọn chia sẻ.
OpenWrite() Tạo ra một FileStream chỉ để ghi dữ liệu.
OpenRead() Tạo ra FileStream chỉ để đọc dữ liệu.
Xem thêm "Thao tác với tập tin và thư mục trong C#":
 
Ví dụ tạo FileStream từ 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;
           }
           // Mở file và cắt hết dữ liệu file hiện tại.
           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 là một class mở rộng từ class Stream, nó là một luồng bộ đệm bao lấy (wrap) một stream khác, giúp nâng cao hiệu quả đọc ghi dữ liệu.
BufferedStream chỉ có 2 cấu tử (Constructor), nó bao lấy một Stream khác.
Cấu tử (Constructor) Mô tả
BufferedStream(Stream) Khởi tạo một đối tượng BufferedStream với kích thước bộ đệm mặc định là 4096 bytes.
System_CAPS_pubmethod   
BufferedStream(Stream, Int32) Khởi tạo một đối tượng BufferedStream với kích thước bộ đệm được chỉ định.
Tôi đưa ra một tình huống, bạn tạo ra một luồng bộ đệm ( BufferedStream) bao lấy FileStream, với mục đích ghi dữ liệu xuống file. Các dữ liệu ghi vào luồng bộ đệm tạm thời sẽ nằm trên bộ nhớ, và khi bộ đệm đầy, dữ liệu tự động được đẩy (Flush) xuống file, bạn có thể chủ động đẩy dữ liệu xuống file bằng cách sử dụng phương thức Flush(). Sử dụng BufferedStream trong trường hợp này giúp giảm số lần phải ghi xuống ổ đĩa, và vì vậy nó làm tăng hiệu suất của chương trình.
Ví dụ dưới đây một BufferedStream bao lấy một luồng ghi file:
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);

           // Đảm bảo thư mục tồn tại.
           file.Directory.Create();

           // Tạo file mới, nếu nó đã tồn tại nó sẽ bị ghi đè.
           // Trả về một đối tượng FileStream.
           using (FileStream fileStream = file.Create())
           {
               // Tạo một đối tượng BufferedStream bao lấy FileStream.
               // (Chỉ định bộ đệm là 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);

                       // Ghi vào bộ đệm, khi bộ đệm đầy nó sẽ tự đẩy xuống file.
                       bs.Write(bytes, 0, bytes.Length);
                   }

                   // Đẩy các dữ liệu còn lại trên bộ đệm xuống file.
                   bs.Flush();
               }
               
           }

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

}
Kết quả chạy ví dụ:

5- MemoryStream

MemoryStream là một class mở rộng trực tiếp từ class Stream, nó là dòng mà dữ liệu được lưu trữ (store) trên bộ nhớ.
Về bản chất MemoryStream là một đối tượng nó quản lý một bộ đệm là một mảng các byte, khi các byte được ghi vào luồng này nó sẽ tự động được gán vào các vị trí tiếp theo tính từ vị trí hiện tại của con trỏ trên mảng. Khi bộ đệm đầy một mảng mới có kích thước lớn hơn được tạo ra, và copy các dữ liệu từ mảng cũ sang.

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)
Ví dụ:
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()
       {

           // Tạo một đối tượng MemoryStream có dung lượng 100 bytes.
           MemoryStream memoryStream = new MemoryStream(100);

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

           // Ghi các byte vào luồng bộ nhớ.
           memoryStream.Write(javaBytes, 0, javaBytes.Length);
           memoryStream.Write(csharpBytes, 0, csharpBytes.Length);

           // Ghi ra sức chứa và độ dài của luồng.
           // ==> Capacity: 100, Length: 10.
           Console.WriteLine("Capacity: {0} , Length: {1}",
                                 memoryStream.Capacity.ToString(),
                                 memoryStream.Length.ToString());

           // Lúc này vị trí con trỏ đang đứng ở sau ký tự 'p'.
           // ==> 10.
           Console.WriteLine("Position: "+ memoryStream.Position);

           // Di chuyển lùi con trỏ đi 6 byte, so với vị trí hiện tại.
           memoryStream.Seek(-6, SeekOrigin.Current);

           // Lúc này vị trí con trỏ đang đứng ở sau ký tự 'a' và trước 'C'.
           // ==> 4.
           Console.WriteLine("Position: " + memoryStream.Position);

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

           
           // Ghi vào luồng bộ nhớ.
           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();

           
       }
   }

}
Chạy ví dụ:

6- UnmanagedMemoryStream

Sử dụng UnmanagedMemoryStream cho phép bạn đọc các luồng dữ liệu không được quản lý mà không cần sao chép tất cả chúng lên quản lý ở bộ nhớ Heap trước sau đó mới được dùng. Nó giúp bạn tiết kiệm bộ nhớ nếu bạn đang phải đối phó với rất nhiều dữ liệu.

Lưu ý rằng có một giới hạn 2GB đối với MemoryStream vì vậy bạn phải sử dụng các UnmanagedMemoryStream nếu bạn vượt quá giới hạn này.
Tôi đưa ra một tình huống: Có các dữ liệu rời rạc nằm sẵn trên bộ nhớ. Và bạn có thể tập hợp chúng lại để quản lý bởi UnmanagedMemoryStream bằng cách quản lý các con trỏ của các dữ liệu rời rạc nói trên, thay vì bạn copy chúng lên luồng để quản lý.

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 là một class sử dụng cho việc mật mã hóa luồng dữ liệu .
Hình ảnh minh họa dưới đây luồng CryptoStream bao lấy một luồng khác (chẳng hạn là luồng ghi file), khi bạn ghi dữ các byte lên CryptoStream các byte này sẽ bị mật mã hóa thành các bytes khác trước khi đẩy sang luồng ghi vào file. Lúc này nội dung của file đã được mật mã hóa.
Chú ý rằng bạn có thể lựa chọn một thuật toán mật mã hóa khi tạo đối tượng CryptoStream.
Trong một tình huống ngược lại, một luồng CryptoStream bao lấy một luồng đọc file (File mà nội dung đã mã hóa ở trên), các byte trên luồng FileStream là các byte đã được mật mã hóa, nó sẽ được giải mật bởi CryptoStream.
Một điều quan trọng bạn cần nhớ rằng, không phải thuật toán mật mã hóa nào cũng có 2 chiều mật mã hóa và giải mật mã hóa.
Hãy xem một ví dụ:

Ở đây tôi sử dụng thuật toán DES để mã hóa và giải mã, bạn cần cung cấp mảng 128 bit nó là chìa khóa bảo mật của bạn.
DES Algorithm
// Đối tượng cung cấp thuật toán mật mã hóa DES.
DESCryptoServiceProvider provider = new DESCryptoServiceProvider();

// Sử dụng khóa riêng của bạn (Phải là chuỗi 128bit = 8byte).
// (Tương đương với 8 ký tự ASCII)
provider.Key = ASCIIEncoding.ASCII.GetBytes("1234abcd");
provider.IV = ASCIIEncoding.ASCII.GetBytes("12345678");


// Đối tượng mật mã hóa.
ICryptoTransform encryptor = provider.CreateEncryptor();


// Đối tượng giải mật mã hóa
ICryptoTransform decryptor = provider.CreateDecryptor();
Xem ví dụ đầy đủ.
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)
       {
           // Cung cấp thuật toán mật mã hóa DES.
           DESCryptoServiceProvider provider = new DESCryptoServiceProvider();

           // Sử dụng khóa riêng của bạn (Phải là chuỗi 128bit = 8byte).
           // (Tương đương với 8 ký tự ASCII)
           provider.Key = ASCIIEncoding.ASCII.GetBytes("1234abcd");
           provider.IV = ASCIIEncoding.ASCII.GetBytes("12345678");


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



           // Một luồng để ghi file.
           using (FileStream stream = new FileStream(encryedFile, FileMode.OpenOrCreate, FileAccess.Write))
           {

               // Đối tượng mật mã hóa.
               ICryptoTransform encryptor = provider.CreateEncryptor();

               // Tạo một luồng mật mã hóa bao lấy luồng ghi file.
               using (CryptoStream cryptoStream = new CryptoStream(stream,
                                    encryptor, CryptoStreamMode.Write))
               {
                   // Một mảng byte chưa được mật mã hóa.
                   byte[] data = ASCIIEncoding.ASCII.GetBytes("Bear, I love you. OK?");

                   // Ghi vào luồng mật mã hóa.
                   cryptoStream.Write(data, 0, data.Length);
               }

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

           // Tiếp theo đọc file mã hóa vừa được tạo ra ở trên.

           // Một luồng để đọc file
           using (FileStream stream = new FileStream(encryedFile, FileMode.Open, FileAccess.Read))
           {
               // Đối tượng giải mật mã hóa
               ICryptoTransform decryptor = provider.CreateDecryptor();

               // Tạo một luồng mật mã hóa bao lấy luồng đọc file.
               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();
       }
   }

}
Chạy ví dụ:
Xem nội dung của file vừa được tạo ra.