CodeGym /Các khóa học /C# SELF /Lớp FileStream

Lớp FileStream

C# SELF
Mức độ, Bài học
Có sẵn

1. Lớp FileStream: làm việc từng phần

FileStream giống như một cái ống nước nối thẳng tới file của bạn trên ổ đĩa. Qua cái ống này, bạn có thể kiểm soát dòng dữ liệu: gửi byte vào file (ghi) hoặc lấy byte từ file (đọc). Khác với các phương pháp cấp cao hơn chỉ "đưa cho bạn một cốc nước", FileStream cho bạn quyền truy cập trực tiếp tới "vòi nước", cho phép bạn điều chỉnh dòng nước (byte) khi cần.

FileStream hoạt động ở mức byte. Nghĩa là nó không quan tâm đó là text hay hình ảnh; với nó, mọi thứ chỉ là chuỗi byte. Nếu bạn làm việc với file text qua FileStream, bạn phải tự chuyển đổi string sang byte (dùng encoding, ví dụ UTF-8) khi ghi và ngược lại khi đọc.

Khi nào nên dùng FileStream?

  • Làm việc với file rất lớn: Khi file quá to, không thể hoặc không nên load hết vào RAM (ví dụ log hàng GB, file video). FileStream cho phép đọc/ghi từng phần (chunk), tiết kiệm bộ nhớ.
  • Dữ liệu nhị phân: Nếu bạn làm với file không phải text (ảnh, audio, video, object đã serialize, file database), FileStream là công cụ chính vì nó cho truy cập byte trực tiếp.
  • Kiểm soát chi tiết chế độ truy cập: Bạn cần mở file để vừa đọc *và* ghi cùng lúc? Hay mở để chương trình khác không truy cập được? Hoặc ngược lại, cho nhiều process cùng đọc? FileStream cho bạn toàn quyền kiểm soát các chế độ này.
  • Thao tác bất đồng bộ: Trong app hiệu năng cao (ví dụ web server), rất quan trọng là thao tác file không chặn luồng chính. FileStream hỗ trợ các method async (ReadAsync, WriteAsync), giúp app luôn phản hồi tốt.
  • Đọc/ghi từng phần hoặc truy cập ngẫu nhiên: Nếu bạn cần đọc dữ liệu từ vị trí cụ thể trong file (ví dụ từ byte 500) hoặc ghi vào giữa file, FileStream cho phép điều khiển vị trí con trỏ file.

Lỗi newbie hay gặp: Dùng FileStream cho mấy việc đơn giản như ghi một dòng text vào file nhỏ. Trường hợp này thì quá thừa. FileStream là tool cho các tình huống đặc biệt và phức tạp hơn.

2. Tạo và mở FileStream

Để bắt đầu làm việc với file qua FileStream, bạn cần tạo một instance của nó. Làm bằng constructor, nhận vài tham số quan trọng quyết định cách bạn tương tác với file.

using System;
using System.IO;   // Bắt buộc để dùng FileStream
using System.Text; // Để làm việc với encoding (chuyển string sang byte và ngược lại)

Ví dụ đọc file text cơ bản bằng FileStream:

Đầu tiên tạo file để có cái mà đọc.

// Tạo file text đơn giản để demo
string path = "example_filestream.txt";

// Mở stream để đọc.
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

// Tạo buffer (mảng byte) để tạm lưu dữ liệu đọc được.
byte[] buffer = new byte[fs.Length];

// Đọc byte từ stream vào buffer.
int bytesRead = fs.Read(buffer, 0, buffer.Length);

// Chuyển byte đọc được về string, dùng encoding phù hợp (ví dụ UTF-8).
string content = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("Đã đọc từ file qua FileStream: " + content);

fs.Close(); //đóng stream

Tham số chính

  • FileMode: mở file kiểu gì (Open, Create, Append OpenOrCreate v.v.)
  • FileAccess: làm gì với file (Read, Write, ReadWrite)
  • FileShare: cho process khác dùng file này cùng lúc không (thường mấy việc đơn giản không cần)

Lỗi thường gặp: Nếu bạn quên đóng FileStream, file có thể bị "khóa" — không xóa hay mở lại được!

3. Tham số constructor của FileStream

Constructor FileStream cho phép bạn cấu hình rất chi tiết cách mở file. Đây là các tham số chính:

new FileStream(
    string path,           // Đường dẫn file (tuyệt đối hoặc tương đối)
    FileMode mode,         // Mở file kiểu gì (tạo mới, mở, ghi đè v.v.)
    FileAccess access,     // Quyền truy cập (chỉ đọc, chỉ ghi, đọc+ghi)
    FileShare share        // Process khác có thể truy cập file khi đang mở không (tùy chọn)
);

Giải thích giá trị enum FileMode và FileAccess:

FileMode (Chế độ mở file):

Xác định OS sẽ xử lý file thế nào khi mở.

  • FileMode.Open: Mở file đã tồn tại. Nếu không tìm thấy file ở đường dẫn, sẽ ném FileNotFoundException.
  • FileMode.Create: Tạo file mới. Nếu file đã tồn tại, nó sẽ bị ghi đè hoàn toàn (dữ liệu cũ bị xóa).

FileAccess (Quyền truy cập file):

Xác định chương trình bạn được phép làm gì với file đã mở.

  • FileAccess.Read: File chỉ mở để đọc. Không ghi được dữ liệu vào.
  • FileAccess.Write: File chỉ mở để ghi. Không đọc được dữ liệu từ file.
  • FileAccess.ReadWrite: File mở để đọc và ghi. Linh hoạt nhất nhưng cũng dễ rối, vì bạn phải tự quản lý vị trí con trỏ stream.

FileShare (Chia sẻ file):

Xác định process khác có thể mở cùng file khi chương trình bạn đang mở không. Rất quan trọng để tránh bị khóa file.

  • FileShare.Read: Process khác có thể đọc file khi bạn đang mở bằng FileStream, nhưng không ghi được.
  • FileShare.Write: Process khác có thể ghi vào file khi bạn đang mở bằng FileStream, nhưng không đọc được.
  • FileShare.ReadWrite: Process khác có thể đọc và ghi vào file. Chế độ này thoải mái nhất nhưng dễ gây xung đột nếu nhiều chương trình cùng sửa file.

Chi tiết về các chế độ sẽ nói ở bài sau.

4. Đọc và ghi dữ liệu qua FileStream

Khi dùng FileStream, bạn thao tác với byte. Nghĩa là với dữ liệu text, bạn phải tự chuyển đổi giữa string và mảng byte, dùng các class encoding (ví dụ System.Text.Encoding.UTF8).

Ghi dữ liệu vào file với FileStream

Khi ghi, bạn chuyển dữ liệu (ví dụ string) thành mảng byte, rồi ghi các byte đó vào stream.

string outputPath = "user_data.txt";
string userName = "Ivan Petrov";

// Chuyển string thành mảng byte, dùng encoding UTF-8
byte[] userNameBytes = Encoding.UTF8.GetBytes(userName);

// Mở FileStream để ghi.
FileStream fsWrite = new FileStream(outputPath, FileMode.Create, FileAccess.Write);

// Ghi mảng byte vào stream.
fsWrite.Write(userNameBytes, 0, userNameBytes.Length);
Console.WriteLine($"Tên '{userName}' đã được ghi vào file '{outputPath}'.");

fsWrite.Close(); //đóng stream

Đọc dữ liệu từ file với FileStream

Khi đọc, bạn lấy byte từ stream vào buffer, rồi chuyển byte đó về format bạn muốn (ví dụ string).

string inputPath = "user_data.txt";

// Mở FileStream để đọc
FileStream fsRead = new FileStream(inputPath, FileMode.Open, FileAccess.Read);

// Tạo buffer bằng kích thước file để đọc hết byte.
byte[] buffer = new byte[fsRead.Length];

// Đọc byte từ stream vào buffer. 
int bytesRead = fsRead.Read(buffer, 0, buffer.Length);

// Chuyển byte đọc được thành string.
string loadedUserName = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"Tên từ file (qua FileStream): {loadedUserName}");

fsRead.Close(); //đóng stream

5. Làm việc với file lớn

Điểm mạnh nhất của FileStream so với các method File.ReadAll... là có thể đọc/ghi file từng phần, rất quan trọng khi làm với file lớn không thể load hết vào RAM.

Đọc file lớn từng phần

Khi đọc file từng phần, bạn dùng buffer (mảng byte) kích thước cố định và đọc dữ liệu vào đó cho đến hết file.

string bigFilePath = "bigfile.bin";     //file lớn 
int bufferSize = 4096;                  // Kích thước buffer, ví dụ 4 KB
byte[] buffer = new byte[bufferSize];   // Tạo buffer
int bytesRead;                          // Số byte thực sự đọc được
long totalBytesRead = 0;                // Tổng số byte đã đọc

// mở stream 
FileStream fs = new FileStream(bigFilePath, FileMode.Open, FileAccess.Read);
int chunkNumber = 1;

// Lặp cho đến khi fs.Read() trả về số byte dương
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
    totalBytesRead += bytesRead;
    Console.WriteLine($"Phần {chunkNumber++}: đã đọc {bytesRead} byte. Tổng: {totalBytesRead} byte.");

    // !!! Xử lý dữ liệu "chunk" đọc được ở đây !!!
   
}

fs.Close(); //đóng stream

Lưu ý quan trọng: Method fs.Read(buffer, offset, count) không đảm bảo sẽ lấp đầy hết buffer. Nó trả về số byte thực sự đọc được, có thể nhỏ hơn count (đặc biệt ở cuối file). Luôn dùng giá trị trả về bytesRead để xử lý dữ liệu đúng.

Ghi dữ liệu lớn từng phần

Tương tự đọc, bạn có thể ghi dữ liệu lớn vào file từng phần.

string bigOutputPath = "big_output.txt";
string longText = new string('A', 1_000_000); // Chuỗi 1 triệu ký tự 'A'
byte[] longTextBytes = Encoding.UTF8.GetBytes(longText);
int writeBufferSize = 1024; // Ghi từng 1 KB

// FileMode.Create: tạo file mới
FileStream fsWrite = new FileStream(bigOutputPath, FileMode.Create, FileAccess.Write);

for (int i = 0; i < longTextBytes.Length; i += writeBufferSize)
{
    // Tính số byte còn lại để ghi ở vòng này
    int bytesToWrite = Math.Min(writeBufferSize, longTextBytes.Length - i);

    // Ghi phần dữ liệu từ longTextBytes, bắt đầu từ offset 'i'
    fsWrite.Write(longTextBytes, i, bytesToWrite);
    Console.WriteLine($"Đã ghi {bytesToWrite} byte");
}

fsWrite.Close(); //đóng stream

6. Những bẫy khó lường và lỗi phổ biến

Ngay cả với tool mạnh như FileStream, vẫn có nhiều thứ bạn cần chú ý:

Buffer và Flush(): Như đã nói, dữ liệu có thể nằm trong buffer RAM mà chưa ghi xuống ổ cứng. Nếu bạn không dùng using và không gọi Close()/Dispose(), mà chỉ kết thúc chương trình, dữ liệu có thể mất. Luôn gọi Flush() (hoặc dùng using/Close()) để đảm bảo dữ liệu được ghi.

Xử lý exception: Lỗi IO (ví dụ IOException khi ghi vào ổ đầy, UnauthorizedAccessException do thiếu quyền, FileNotFoundException khi mở file không tồn tại ở chế độ Open) là chuyện thường. Luôn bọc thao tác với FileStream trong block try-catch.

Encoding: Làm việc với text qua FileStream (chỉ thao tác byte), bạn phải tự chịu trách nhiệm chọn encoding đúng (Encoding.UTF8, Encoding.ASCII, Encoding.Unicode v.v.) khi chuyển string sang byte và ngược lại. Chọn sai encoding sẽ ra "ký tự lạ".

Quản lý vị trí: Nếu dùng Seek(), chú ý tham số offset và origin, kẻo nhảy ra ngoài file hoặc tới chỗ không mong muốn.

Khóa file (FileShare): Nếu mở file với FileShare.None, process khác không truy cập được. Có thể gây vấn đề nếu chương trình khác (hoặc thread khác trong app bạn) cũng muốn dùng file đó. Luôn chọn chế độ FileShare phù hợp nhất.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Đọc file văn bản sử dụng FileStream
Đọc file văn bản sử dụng FileStream
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION