1. Class File: đơn giản và hiệu quả
Class System.IO.File hiện thực hóa ý tưởng "một thao tác — một method". Nó cung cấp một loạt các method cấp cao, đóng gói logic phức tạp khi làm việc với hệ điều hành, giúp bạn tập trung vào task của mình.
Class File làm được gì?
- Ghi text hoặc dữ liệu binary bất kỳ vào file.
- Đọc nội dung file toàn bộ hoặc theo dòng (với file text).
- Sao chép, xóa và đổi tên file đã có.
- Kiểm tra sự tồn tại của file theo đường dẫn chỉ định.
- Làm việc với thuộc tính file (ví dụ, ngày tạo, kích thước – qua các class liên quan).
Tại sao lại tiện?
Tất cả method của File đều là static. Nghĩa là bạn không cần tạo object, chỉ cần gọi method cần thiết, ví dụ:
File.WriteAllText("data.txt", "Xin chào, file!");
Kiểu này rất giống với class Console: mọi thứ đều đơn giản, không cần "khởi tạo", không phải lo về stream.
Nếu bạn muốn có một công cụ đa năng và tiện lợi để làm việc với file, class System.IO.File chính là thứ đó. Nó cung cấp rất nhiều method để đọc, ghi, copy, xóa và các thao tác cơ bản khác với file, như một con dao đa năng cho hệ thống file.
Làm sao dùng File?
Tất cả method của class File đều là static, nên không cần tạo object — chỉ cần viết File.Gì đó(). Rất giống với Console.WriteLine(), chỉ là giờ dùng cho file.
string text = "Xin chào, thế giới!";
string path = "hello.txt";
// Ghi text vào file (nếu file đã có, sẽ ghi đè!)
File.WriteAllText(path, text);
// Đọc toàn bộ file thành một string
string readText = File.ReadAllText(path);
Console.WriteLine("Nội dung file: " + readText);
Mẹo nhỏ: Nếu bạn viết ReadAllText và WriteAllText 10 lần liên tiếp, có thể bạn đang làm sai — các method này chỉ nên dùng cho file nhỏ và đơn giản thôi.
Các method thường dùng của class File
| Method | Mô tả |
|---|---|
|
Kiểm tra file có tồn tại ở đường dẫn chỉ định không |
|
Xóa file |
|
Sao chép file |
|
Di chuyển (hoặc đổi tên) file |
|
Đọc toàn bộ file thành một string |
|
Ghi string vào file (ghi đè nếu đã có) |
|
Đọc tất cả dòng của file thành mảng string |
|
Ghi mảng string vào file (mỗi string là một dòng) |
|
Đọc tất cả byte của file thành mảng byte |
|
Ghi mảng byte vào file |
2. Ghi và đọc file text
Cùng xem các method thường dùng nhất của class File với ví dụ thực tế. Để dùng chúng, luôn cần chỉ thị using System.IO; ở đầu file code của bạn.
using System;
using System.IO; // Bắt buộc để dùng class File
using System.Linq; // Có thể cần cho ReadLines
Đọc file toàn bộ
Đây là một task rất phổ biến: lưu dữ liệu text vào file hoặc tải lại từ file.
Ghi một dòng (hoặc toàn bộ text) vào file: File.WriteAllText() Method này tạo file mới ở đường dẫn chỉ định. Nếu file đã tồn tại, nội dung sẽ bị ghi đè hoàn toàn bằng text mới.
string myFilePath = "notes.txt";
string textContent = "Hôm nay là ngày tuyệt vời để học C#.\nĐừng quên làm bài tập về nhà.";
// Ghi toàn bộ text vào file
File.WriteAllText(myFilePath, textContent);
Console.WriteLine($"File '{myFilePath}' đã được tạo/ghi đè thành công.");
Đọc file toàn bộ thành một string: File.ReadAllText() Method này đọc toàn bộ nội dung file text từ đầu đến cuối và trả về một string lớn.
string myFilePath = "notes.txt";
// Lưu ý: luôn kiểm tra file có tồn tại trước khi đọc để tránh lỗi!
if (File.Exists(myFilePath))
{
string content = File.ReadAllText(myFilePath);
Console.WriteLine($"Nội dung file '{myFilePath}':\n{content}");
}
else
{
Console.WriteLine($"Lỗi: File '{myFilePath}' không tìm thấy để đọc.");
}
Lưu ý quan trọng: Các method File.WriteAllText() và File.ReadAllText() rất phù hợp cho file nhỏ và vừa (ví dụ, ghi chú, file cấu hình, log ngắn). Nhưng nếu file rất lớn (vài chục, vài trăm MB, hoặc cả GB), việc tải toàn bộ nội dung vào RAM có thể ngốn rất nhiều bộ nhớ và thậm chí gây ra OutOfMemoryException. Với trường hợp đó, nên dùng cách khác (xem bên dưới và các bài sau).
3. Đọc file theo dòng
Khi bạn làm việc với file text lớn, như báo cáo CSV, file log dài hoặc database text, đọc toàn bộ vào RAM (tức là tải hết từ ổ đĩa vào bộ nhớ) sẽ không hiệu quả hoặc không thể. Lúc này, File.ReadLines() là cứu tinh.
File.ReadLines(path) — method này không tải toàn bộ file một lần. Thay vào đó, nó trả về một collection đặc biệt (IEnumerable<string>), cho phép bạn đọc file dòng nào cần thì đọc dòng đó. Cái này gọi là "đọc lười" hoặc "đọc theo stream".
string logFilePath = "application.log";
string text = """
INFO: Ứng dụng đã khởi động.
WARN: Phát hiện vấn đề ở module X.
ERROR: Lỗi mạng nghiêm trọng.
INFO: Ứng dụng đã dừng.
""";
// Giả sử có file log với nhiều dòng.
File.WriteAllText(logFilePath, text);
// Mỗi vòng lặp chỉ đọc và xử lý MỘT dòng
foreach (string line in File.ReadLines(logFilePath))
Console.WriteLine($"- {line}");
Ưu điểm: File được đọc và xử lý từng phần, giảm đáng kể việc dùng RAM khi làm với file lớn.
Khác biệt giữa File.ReadLines() và File.ReadAllLines():
- File.ReadAllLines(path): Tải toàn bộ dòng của file vào RAM và trả về mảng string[]. Sau khi gọi, toàn bộ file đã nằm trong bộ nhớ. Tiện nếu bạn cần truy cập tất cả dòng cùng lúc (ví dụ, sort hoặc tìm kiếm).
- File.ReadLines(path): Trả về IEnumerable<string>, cho phép duyệt từng dòng. File chỉ được đọc khi bạn lấy dòng tiếp theo (ví dụ, trong vòng lặp foreach). Hiệu quả hơn cho file rất lớn, khi bạn không thể hoặc không muốn tải hết vào RAM.
4. Ghi mảng string
Đôi khi bạn có một list string (ví dụ, từ một collection), và muốn ghi mỗi string thành một dòng riêng trong file.
File.WriteAllLines(path, contents)
Method này nhận mảng string (hoặc collection string) và ghi mỗi string thành một dòng riêng trong file. Giống như WriteAllText(), nó sẽ ghi đè file nếu đã tồn tại.
string membersFilePath = "participants.txt";
string[] names = { "Ivan Petrov", "Maria Sidorova", "Sergey Kozlov", "Anna Kuznetsova" };
// Mỗi dòng của mảng sẽ được ghi thành một dòng trong file
File.WriteAllLines(membersFilePath, names);
// Kiểm tra nội dung
Console.WriteLine($"Nội dung '{membersFilePath}':");
foreach (string line in File.ReadLines(membersFilePath))
Console.WriteLine($"- {line}");
5. Làm việc với file binary
Class File không chỉ dùng cho dữ liệu text. Nó còn có các method tiện lợi để đọc và ghi dữ liệu binary, tức là byte thô. Cái này hữu ích khi lưu cấu hình dạng đặc biệt, làm với ảnh, audio hoặc bất kỳ dữ liệu nào không phải text.
Ghi mảng byte: File.WriteAllBytes() Tạo hoặc ghi đè file, ghi toàn bộ mảng byte vào đó.
string binaryDataPath = "settings.bin";
// Ví dụ dữ liệu binary: ảnh, video, bất kỳ dữ liệu nào
byte[] settingsData = { 0x01, 0x0A, 0x2C, 0xFF, 0x00, 0x3F }; // Một số "cài đặt" thô
File.WriteAllBytes(binaryDataPath, settingsData);
Console.WriteLine($"Dữ liệu binary đã được ghi vào file '{binaryDataPath}'.");
Lưu ý: nếu bạn mở 'settings.bin' bằng Notepad, bạn sẽ thấy ký tự lạ ("ký tự rác"), vì các byte này không phải để đọc như text.
Đọc mảng byte: File.ReadAllBytes() Đọc toàn bộ nội dung file binary thành mảng byte.
if (File.Exists(binaryDataPath))
{
byte[] readSettings = File.ReadAllBytes(binaryDataPath);
Console.WriteLine($"Đã đọc {readSettings.Length} byte từ file '{binaryDataPath}'.");
Console.Write("Byte đã đọc (dạng hex): ");
foreach (byte b in readSettings)
Console.Write($"{b:X2} "); // :X2 format byte thành số hex 2 chữ số
Console.WriteLine();
}
else
{
Console.WriteLine($"Lỗi: File binary '{binaryDataPath}' không tìm thấy.");
}
Các method này rất tiện để lưu trữ một lượng nhỏ dữ liệu binary, ví dụ cache ảnh nhỏ hoặc object đã serialize.
6. Quản lý file trên ổ đĩa
Class File cũng cung cấp các method để quản lý file trên ổ đĩa.
Kiểm tra file tồn tại: File.Exists(path) Trả về true nếu file tồn tại ở đường dẫn chỉ định, false nếu không. Rất nên dùng method này trước khi đọc, xóa hoặc thao tác khác có thể gây lỗi nếu file không tồn tại.
string myFileToCheck = "report.txt";
// Tạo file ví dụ
File.WriteAllText(myFileToCheck, "Báo cáo thử nghiệm.");
if (File.Exists(myFileToCheck))
{
Console.WriteLine($"File '{myFileToCheck}' đã tìm thấy.");
// Giờ có thể đọc, xóa, v.v. an toàn
}
else
{
Console.WriteLine($"File '{myFileToCheck}' không tìm thấy.");
}
Xóa file: File.Delete(path) xóa file khỏi ổ đĩa vĩnh viễn. Cẩn thận, thao tác này không thể hoàn tác!
string fileToDelete = "temp_file.txt";
// Tạo file ví dụ
File.WriteAllText(fileToDelete, "File này sẽ bị xóa.");
if (File.Exists(fileToDelete))
{
File.Delete(fileToDelete);
Console.WriteLine($"File '{fileToDelete}' đã xóa thành công.");
}
else
{
Console.WriteLine($"File '{fileToDelete}' đã không còn hoặc đã bị xóa trước đó.");
}
7. Exception khi làm việc với file
Làm việc với file luôn liên quan đến hệ điều hành và tài nguyên ngoài, nên rất dễ gặp lỗi.
Hãy xử lý exception (lỗi)! Khi làm với file có thể gặp nhiều vấn đề mà chương trình của bạn nên xử lý đúng cách, chứ không chỉ "crash". Các tình huống điển hình:
- FileNotFoundException: File bạn muốn truy cập không tồn tại ở đường dẫn chỉ định.
- DirectoryNotFoundException: Thư mục bạn muốn tạo hoặc tìm file không tồn tại.
- UnauthorizedAccessException: Chương trình không có quyền đọc, ghi hoặc xóa file/thư mục (ví dụ, file hệ thống hoặc ở vùng bảo vệ).
- IOException: Lỗi I/O chung, thường xảy ra khi file đang bị chương trình khác mở và khóa, hoặc hết dung lượng ổ đĩa.
Luôn dùng try-catch để làm việc với file an toàn. Như vậy bạn có thể "bắt" lỗi và xử lý phù hợp (ví dụ, báo cho user, ghi log).
string sensitiveFilePath = "secret_data.txt";
try
{
// Thử đọc file có thể không tồn tại hoặc không có quyền truy cập
string secretContent = File.ReadAllText(sensitiveFilePath);
Console.WriteLine($"Đã đọc nội dung bí mật: {secretContent}");
}
catch (FileNotFoundException) // Lỗi cụ thể: file không tìm thấy
{
Console.WriteLine($"Lỗi: File '{sensitiveFilePath}' không tìm thấy. Hãy kiểm tra lại đường dẫn.");
}
catch (IOException ex) // Lỗi I/O chung (ví dụ, file bị khóa)
{
Console.WriteLine($"Lỗi I/O khi làm với file '{sensitiveFilePath}': {ex.Message}");
}
catch (Exception ex) // Bất kỳ lỗi bất ngờ nào khác
{
Console.WriteLine($"Đã xảy ra lỗi không mong đợi: {ex.Message}");
}
8. Khi nào System.IO.File không phải lựa chọn tốt nhất?
Dù class File rất đa năng, vẫn có những trường hợp dùng nó không tối ưu hoặc không thể:
File cực lớn (hàng chục+ GB): Các method ReadAllText()/ReadAllBytes() sẽ tải toàn bộ file vào RAM. Với file cực lớn, sẽ gây OutOfMemoryException. Khi đó nên dùng File.ReadLines() (cho text) hoặc dùng stream (FileStream) để đọc/ghi từng phần. (Về stream sẽ nói ở bài sau).
Kiểm soát truy cập file chi tiết: Nếu bạn cần kiểm soát rất cụ thể cách mở file (ví dụ, chỉ mình bạn được đọc/ghi, hoặc cho phép nhiều process đọc cùng lúc), File không hỗ trợ chi tiết như vậy. Khi đó cần dùng các class stream cấp thấp hơn.
Thao tác bất đồng bộ: Với app hiệu năng cao, cần thao tác file mà không block thread chính (ví dụ, web server), cần dùng method async (như ReadAllTextAsync()), cũng dựa trên stream.
Đọc/ghi từng phần: Nếu bạn chỉ muốn đọc một phần file từ offset nào đó hoặc ghi vào giữa file, các method của File không làm được. Khi đó phải dùng stream trực tiếp.
9. Tổng kết
Tại sao System.IO.File lại phổ biến?
- Đơn giản và ngắn gọn: Các method static cho phép thao tác phức tạp chỉ với 1-2 dòng code. Giúp code dễ đọc và phát triển nhanh.
- Tự động quản lý tài nguyên: File tự lo mở/đóng file cho mỗi thao tác, bạn không phải tự quản lý (giảm lỗi).
- Đa năng cho task thường ngày: Với hầu hết thao tác với file text và binary nhỏ/vừa, File là quá đủ.
- Phổ biến: Trong hầu hết task thực tế và phỏng vấn, bạn được kỳ vọng biết dùng các method của class File.
- Ẩn đi sự phức tạp: Các method được viết để đóng gói logic phức tạp khi làm việc với hệ điều hành, cho bạn interface sạch và dễ hiểu.
Câu hỏi thường gặp và giải thích
Kích thước file tối đa cho File.ReadAllText() là bao nhiêu?
Với file nhỏ hơn vài chục MB (< 100 MB) thường không có vấn đề gì. Nếu file lớn hơn (hàng trăm MB, GB), dùng File.ReadAllText() có thể hết RAM (exception OutOfMemoryException). Khi đó nên dùng File.ReadLines() để đọc từng dòng hoặc công cụ stream nâng cao (sẽ học sau).
Có thể tạo/ghi file trong thư mục chưa tồn tại không?
Không. Các method File.WriteAllText(), File.WriteAllBytes() và các method khác không tự tạo thư mục cha. Nếu bạn chỉ định đường dẫn C:\MyAppData\Logs\current.log, mà thư mục MyAppData hoặc Logs chưa có, sẽ bị DirectoryNotFoundException. Bạn cần đảm bảo thư mục đã tồn tại, dùng class System.IO.Directory (ví dụ, Directory.CreateDirectory("myfolder")).
Làm sao biết ngày tạo, ngày sửa hoặc kích thước file?
Để lấy thông tin chi tiết về file như ngày tạo, ngày sửa cuối, kích thước, thuộc tính (ẩn, chỉ đọc, v.v.), dùng class System.IO.FileInfo. Nó có rất nhiều property hữu ích.
GO TO FULL VERSION