CodeGym /Các khóa học /C# SELF /Đọc file văn bản: StreamRe...

Đọc file văn bản: StreamReader

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

1. Giới thiệu

Giả sử bạn lại có nhiệm vụ đọc nội dung file văn bản. Ví dụ, bạn có file chứa các câu nói hay của lập trình viên, muốn hiển thị trong app console quen thuộc của mình. Đúng là mình đã từng dùng mấy hàm cao cấp như File.ReadAllText, nhưng nếu file to quá thì sao, hoặc muốn đọc từng dòng một cho tiết kiệm bộ nhớ thì sao? Hoặc muốn đọc file kiểu kiểm soát, ví dụ load chậm từng dòng để không bị "ngốn RAM"?

Đó là lúc StreamReader xuất hiện — class này cực hợp để đọc file text từng dòng, từng block, từng ký tự — nói chung là rất linh hoạt và tiện.

StreamReader hoạt động thế nào?

StreamReader là class trong namespace System.IO, dùng để đọc dữ liệu text từ stream (thường là file, nhưng cũng có thể là stream mạng hoặc bộ nhớ). Nó biết đọc dữ liệu đúng encoding, chuyển thành ký tự và chuỗi, trả về cho bạn kiểu dữ liệu tiện dùng: string và char.

Nếu vẽ sơ đồ thì sẽ kiểu như này:


[ Tệp (bytes) ] --(FileStream)--> [ StreamReader (giải mã ký tự) ] ---> [ Code của bạn (string, char) ]
  • Tệp (hoặc nguồn bytes khác): Cho mình bytes.
  • FileStream: Đọc bytes như một "kênh".
  • StreamReader: Chuyển bytes thành ký tự và chuỗi theo encoding (mặc định — UTF-8).

Tại sao không phải lúc nào cũng dùng File.ReadAllText?

Thực tế file có thể rất to. Rất rất to (ví dụ log hoặc csv cả triệu dòng). Đọc hết file vào RAM một phát — giống như ăn hết cái bánh sinh nhật một lần: ngon nhưng nguy hiểm cho sức khỏe.
StreamReader đọc file "từng miếng". Nó không load hết file vào RAM, mà chỉ đọc và trả về dòng/ký tự tiếp theo khi bạn cần. Rất tiết kiệm và tiện.

2. Đọc file từng dòng

Tạo file quotes.txt với mấy dòng như này:


Code — là thơ ca.
Debug — là thám tử.
"Không chạy" — bug report đỉnh nhất.

Giờ thêm code vào app học tập mình đang làm (giả sử là app console đơn giản, mình phát triển dần). Đặt file cạnh .exe — để chương trình đọc và in từng dòng ra màn hình.

Code có chú thích:


// Lấy đường dẫn file trong thư mục chương trình
string fileName = "quotes.txt";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

// Kiểm tra file có tồn tại không
if (!File.Exists(filePath))
{
    Console.WriteLine($"Không tìm thấy file: {filePath}");
    return;
}

// Mở file để đọc bằng StreamReader
using StreamReader reader = new StreamReader(filePath);
string? line;
int lineNumber = 1;

// Đọc file từng dòng đến hết
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine($"{lineNumber,2}: {line}");
    lineNumber++;
}

Ở đây có gì?

  • Mình tính đường dẫn file bằng Path.CombineAppDomain.CurrentDomain.BaseDirectory — code chạy giống nhau trên Windows, Linux, thậm chí Docker.
  • Kiểm tra file có thật không.
  • Dùng using tạo StreamReader và đọc từng dòng (hàm ReadLine()).
  • Kết thúc khối using thì file tự đóng, kể cả bạn quên hoặc có lỗi.

Minh họa: sơ đồ hoạt động


[ quotes.txt ] --(FileStream)--> [ StreamReader ] --(ReadLine)--> [ string line ] --(Console.WriteLine)--> Màn hình
    ^                                                       
    |
AppDomain.CurrentDomain.BaseDirectory + Path.Combine

3. Lưu ý và chi tiết khi dùng

Cách đọc từng dòng

Hàm ReadLine() trả về chuỗi đến ký tự xuống dòng đầu tiên (\n hoặc \r\n). Khi hết file, trả về null. Vì vậy vòng lặp thường như này:


string? line;
while ((line = reader.ReadLine()) != null)
{
    // xử lý dòng
}

Lỗi thường gặp khi dùng StreamReader

Đôi khi bạn lười không dùng using, mà viết kiểu này:


StreamReader reader = new StreamReader(filePath);
// ...
reader.Close();

Cách này — như quên đóng cửa khi trời lạnh: chuyện gì cũng có thể xảy ra. Nếu đang đọc mà lỗi (ví dụ mất quyền truy cập ổ đĩa), code đóng file sẽ không chạy, file sẽ bị "treo" mở trong hệ thống.
Vì vậy dùng using — không phải khuyến nghị mà là bắt buộc. Admin hệ thống tương lai của bạn sẽ cảm ơn vì chương trình không để lại "vườn thú" file bị treo.

Viết ngắn gọn với using declaration

Từ C# 8.0 trở lên, bạn có thể viết ngắn hơn:


using StreamReader reader = new StreamReader(filePath);
string? line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine(line);
}

Dispose sẽ tự gọi khi kết thúc block (method).

4. Một số chi tiết hữu ích

Đọc file với encoding không xác định

Mặc định StreamReader dùng UTF-8. Nhưng đôi khi gặp file encoding khác (ví dụ Windows-1251 cho text tiếng Nga cũ). Khi đó chương trình có thể in ra "?" hoặc ký tự lạ.

Bạn có thể chỉ định encoding rõ ràng:


using System.Text;

// Mở file với Windows-1251
using StreamReader reader = new StreamReader(filePath, Encoding.GetEncoding("windows-1251"));

Tài liệu về encoding và Encoding

Đọc file thành một chuỗi lớn

Đôi khi cần lấy toàn bộ file thành một chuỗi, nhưng không dùng File.ReadAllText, mà dùng StreamReader.


using StreamReader reader = new StreamReader(filePath);
string allText = reader.ReadToEnd();
Console.WriteLine(allText);
  • Hàm ReadToEnd() đọc toàn bộ file từ vị trí hiện tại đến hết thành một chuỗi.
  • Với file lớn thì có thể không hiệu quả và dễ bị OutOfMemoryException. Với file vài MB thì ổn.

Dùng thực tế như thế nào

  • Xử lý log. Nếu bạn có file log server, đọc từng dòng, lọc sự kiện cần thiết mà không phải load hết vào RAM.
  • Import CSV. Nếu cần parse bảng lớn, đọc từng dòng và xử lý dần.
  • Kiểm tra từ khóa trong file text lớn. Bạn có thể tìm text cần trong hàng triệu dòng mà không lo hết RAM.
  • Unit test. File test data thường đọc từng dòng bằng StreamReader.

So sánh các cách đọc

Cách Khi nào dùng Ưu điểm Nhược điểm
File.ReadAllText
File nhỏ Một dòng code, nhanh File to – tốn RAM
StreamReader.ReadLine()
Bất kỳ, đặc biệt file lớn Đọc từng dòng, tiết kiệm RAM Code dài hơn, logic phức tạp hơn
StreamReader.ReadToEnd()
File nhỏ/vừa Linh hoạt chọn encoding Khó dùng với file rất lớn

5. Thực hành

Giờ nâng cấp bài tập. Để chương trình chỉ in ra N dòng đầu của file, số dòng do user nhập. Đôi khi không muốn xem cả trăm dòng — chỉ cần vài câu quote để lấy động lực. :)

Code như sau:


using System;
using System.IO;

class Program
{
    static void Main()
    {
        string fileName = "quotes.txt";
        string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

        if (!File.Exists(filePath))
        {
            Console.WriteLine($"Không tìm thấy file: {filePath}");
            return;
        }

        Console.Write("In bao nhiêu dòng? ");
        string? input = Console.ReadLine();
        if (!int.TryParse(input, out int linesToShow) || linesToShow < 1)
        {
            Console.WriteLine("Lỗi: Nhập số hợp lệ lớn hơn 0.");
            return;
        }

        using StreamReader reader = new StreamReader(filePath);

        int current = 0;
        string? line;
        while (current < linesToShow && (line = reader.ReadLine()) != null)
        {
            Console.WriteLine(line);
            current++;
        }

        if (current == 0)
            Console.WriteLine("File rỗng hoặc không đọc được dòng đầu.");
        else if (current < linesToShow)
            Console.WriteLine($"File chỉ có {current} dòng.");
    }
}

6. Những điểm quan trọng và bẫy thường gặp

Khi dùng StreamReader, quan trọng nhất là quản lý resource đúng cách (xem lại bài trước về IDisposable). Nếu quên, bạn sẽ gặp lỗi "file đang được dùng bởi process khác" hoặc "quá nhiều file mở".

Một lưu ý nữa — cẩn thận với encoding. Nếu không chắc file là UTF-8, hãy chỉ định encoding rõ ràng. Có tham số thứ hai trong constructor StreamReader cho việc này.

Một "bẫy" thực tế nữa là nếu file bị cập nhật khi chương trình đang chạy (ví dụ file log). Không phải lúc nào bạn cũng thấy dòng mới ngay nếu file đang bị process khác dùng — khi đó cần đọc lại file hoặc dùng phương pháp đặc biệt, nhưng cái này để sau nói tiếp.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Đọc N dòng đầu tiên của file
Đọc N dòng đầu tiên của file
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION