openplanning

Hướng dẫn sử dụng Stream - luồng vào ra nhị phân trong C#

  1. Tổng quan về Stream
  2. Ví dụ cơ bản Stream
  3. FileStream
  4. BufferedStream
  5. MemoryStream
  6. UnmanagedMemoryStream
  7. CryptoStream

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ừ lớp 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.
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 lớp 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 phương thức khởi tạo (Constructor) của class con. Lớp 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 (property) 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 (stream). 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).
Ví dụ luồng ghi dữ liệu
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 thông qua constructor của 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 các byte (1byte < 2^8).
                // Mảng này 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ị
H72W87
e101r114
l108d100
o11132
!33
Bạn có thể tham khảo thêm về bảng mã ASCII tại:
Ví dụ luồng đọc dữ liệu
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;
            }

            // Tạo một đối tượng Stream thông qua Constructor của lớp 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 Stream và 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 vừa đọc được.
                while ((len = readingStream.Read(temp, 0, temp.Length)) > 0)
                {
                    // Chuyển thành chuỗi (String).
                    // ('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 lớp mở rộng từ lớp 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 (property), 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ành 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 cha 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:
Phương thức khởi tạo (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.
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 cụt (Truncate) hết dữ liệu 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 lớp mở rộng từ lớp Stream, nó là một luồng (stream) 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 phương thức khởi tạo (Constructor), nó bao lấy một stream khác.
Phương thức khởi tạo (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.
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 dữ liệu vào 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 (buffer) có sức chứa 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 (buffer), 
                        // khi bộ đệm đầy nó sẽ tự động đẩy dữ liệu 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 lớp mở rộng trực tiếp từ lớp Stream, nó là luồng (stream) 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 (buffer) 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 memoryStream (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 Stream.
            // ==> Capacity: 100, Length: 10.
            Console.WriteLine("Capacity: {0} , Length: {1}",
                                  memoryStream.Capacity.ToString(),
                                  memoryStream.Length.ToString());

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

            // Di chuyển lùi con trỏ lạ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 dữ liệu vào memoryStream (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 khi sử 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ỏ (pointer) 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 (stream) để 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 lớp, 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 byte 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 (encrypt), nó sẽ được giải mật (decrypt) 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 tư (private key) của bạn (Phải là một 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");

// Bộ mật mã hóa (Encryptor).
ICryptoTransform encryptor = provider.CreateEncryptor();

// Bộ giải mật mã hóa (Decrytor).
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();

            // Khóa bảo mật (secret key) (Phải là một 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 (Encryptor).
                ICryptoTransform encryptor = provider.CreateEncryptor();

                // Tạo một CryptoStream bao lấy FileStream. 
                // (FileStream trong trường hợp này dùng để ghi dữ liệu vào 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 cryptoStream.
                    cryptoStream.Write(data, 0, data.Length);
                } 

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

            // Tiếp theo đọc file đã được mật mã hóa, vừa được tạo ra ở bước trên. 
            using (FileStream stream = new FileStream(encryedFile, FileMode.Open, FileAccess.Read))
            {
                // Đối tượng giải mật mã hóa (Decryptor).
                ICryptoTransform decryptor = provider.CreateDecryptor();

                // Tạo một CryptoStream bao lấy FileStream.
                // (FileStream trong trường hợp này dù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.