1. Giới thiệu
Khi bạn làm việc với file qua FileStream hoặc các class stream khác, rất quan trọng là phải hiểu trước điều gì sẽ xảy ra với file khi bạn cố mở nó. Thường có tình huống file đã tồn tại, và bạn muốn hoặc là thêm dữ liệu mới mà không mất dữ liệu cũ, hoặc ghi đè toàn bộ file. Ngược lại, cũng có lúc file chưa được tạo, và bạn muốn đọc nó mà không vô tình tạo ra file rỗng.
Ví dụ, nếu bạn viết một app ghi chú hàng ngày, nhiệm vụ của bạn là thêm ghi chú mới vào cuối file đã có, giữ lại mọi thứ đã ghi trước đó. Nghĩa là bạn cần mở file ở chế độ thêm (append). Trường hợp khác — ví dụ bạn có file log, mỗi lần chạy app phải tạo lại từ đầu để không lẫn dữ liệu cũ. Lúc này cần mở file sao cho khi mở nó sẽ bị xóa sạch hoặc tạo mới nếu chưa có. Còn nếu bạn chỉ cần đọc file đã có, chắc chắn bạn sẽ muốn đảm bảo không tạo ra file mới nếu nó chưa tồn tại, nếu không bạn sẽ có một file rỗng ở nơi không nên có.
Tất cả các tình huống này — thêm, ghi đè, đọc mà không tạo — đều được C# xử lý qua các chế độ mở file đặc biệt, được chỉ định bằng enum System.IO.FileMode. Mỗi chế độ kiểm soát hành vi khi mở hoặc tạo file, giúp bạn kiểm soát chính xác điều gì xảy ra với file khi thao tác với nó.
2. Enum FileMode — giải thích dễ hiểu
Dưới đây là các giá trị chính của enum FileMode:
| Giá trị | Mô tả | Xử lý nếu file đã có | Xử lý nếu file chưa có |
|---|---|---|---|
|
Tạo file mới. Nếu file đã có — ném exception | Lỗi | Ok, tạo file |
|
Tạo file mới nếu chưa có, hoặc ghi đè file đã có | Ghi đè | Ok, tạo mới |
|
Mở file đã có. Nếu chưa có — exception | Ok, mở file | Lỗi |
|
Mở nếu có, nếu không — tạo mới | Ok, mở file | Ok, tạo mới |
|
Mở file đã có và xóa sạch nội dung (dữ liệu bị xóa) | Ok, xóa file | Lỗi |
|
Mở file để ghi thêm vào cuối, nếu chưa có — tạo mới | Ok, thêm vào cuối | Ok, tạo mới |
Minh họa: Kịch bản hành vi
flowchart TD
Start[Bắt đầu]
A{File tồn tại?}
B1[Mở để ghi thêm]
B2[Tạo file mới]
B3[Ghi đè file]
B4[Mở file]
B5[Lỗi: không tìm thấy file]
B6[Lỗi: file đã tồn tại]
Start --> A
A --Append, OpenOrCreate--> B1
A --Create, Truncate--> B3
A --Open, Truncate--> B4
A --CreateNew--> B6
A --Create, OpenOrCreate, Append--> B2
A --Open, Truncate--> B5
3. Ví dụ mở file ở các chế độ khác nhau
Cùng thử thực hành với mini-app của chúng ta, nơi ta ghi lời chào và ngày vào một file text. Ta sẽ xét ba kịch bản phổ biến:
- A. Tạo file mới (CreateNew)
- B. Ghi đè file (Create)
- C. Thêm dòng vào cuối file (Append)
Tạo file mới (FileMode.CreateNew)
Nếu file đã có, chế độ này đảm bảo bạn không thể tạo lại: chương trình sẽ ném IOException. Dùng khi không được phép mất dữ liệu hoặc xóa nhầm file của người khác.
using System;
using System.IO;
string filePath = "greeting.txt";
// Thử tạo file mới — nếu đã có sẽ exception!
try
{
var stream = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write);
var writer = new StreamWriter(stream);
writer.WriteLine("Xin chào! Đây là ghi đầu tiên.");
writer.WriteLine($"Ngày: {DateTime.Now}");
Console.WriteLine("Tạo file thành công.");
writer.Close();
}
catch (IOException)
{
Console.WriteLine($"File '{filePath}' đã tồn tại! Không ghi đè.");
}
Lưu ý: nếu chạy code này lần hai — catch sẽ chạy, dữ liệu không bị ghi đè.
Ghi đè file (FileMode.Create)
Chế độ này không nể nang gì: nếu file đã có, nó sẽ bị xóa sạch. Nếu chưa có — tạo mới. Tiện khi cần luôn bắt đầu "từ đầu". Ví dụ, lưu lại báo cáo.
using System;
using System.IO;
string filePath = "greeting.txt";
// Tạo hoặc ghi đè file hoàn toàn
var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
var writer = new StreamWriter(stream);
writer.WriteLine("Xin chào! Tôi vừa cập nhật lại file :)");
writer.WriteLine($"Cập nhật: {DateTime.Now}");
Console.WriteLine("File đã được tạo hoặc ghi đè.");
writer.Close();
Chạy code này nhiều lần — file luôn chỉ có các dòng mới nhất. Dữ liệu cũ biến mất hoàn toàn.
Thêm vào cuối file (FileMode.Append)
Chế độ tuyệt vời cho log, nhật ký sự kiện, ghi nhận lượt truy cập v.v. Nếu file chưa có — tạo mới. Nếu đã có — thêm vào cuối.
using System;
using System.IO;
string filePath = "greeting.txt";
// Thêm dòng mới vào cuối file
var stream = new FileStream(filePath, FileMode.Append, FileAccess.Write);
var writer = new StreamWriter(stream);
writer.WriteLine($"Thêm một lời chào nữa! Ngày: {DateTime.Now}");
Console.WriteLine("Đã thêm dòng mới vào cuối file.");
writer.Close();
Thử chạy code này nhiều lần liên tiếp. File sẽ lớn dần, mỗi ghi chú mới nằm ở cuối.
4. Chọn chế độ mở file như thế nào? Tips thực tế
Kịch bản thực tế:
- Ghi log: gần như luôn dùng Append. Mất log do ghi đè — admin đau đầu, hacker vui mừng.
- Xuất báo cáo: thường dùng Create, để không làm rác thư mục với bản cũ.
- Xuất dữ liệu quan trọng: nên dùng CreateNew. Nếu file đã có — báo lỗi, không động vào.
- Đọc file: không cần chế độ tạo, dùng FileMode.Open — nếu file không có, đó là vấn đề cần xử lý.
Khi code C#, bạn thường dùng các static method của class File, bên trong tự chọn chế độ phù hợp. Ví dụ, File.AppendAllText luôn dùng Append, còn File.WriteAllText — dùng Create.
5. Chế độ file và các vấn đề thường gặp
Một số điểm dễ gây nhầm lẫn:
Nếu bạn mở file với FileMode.Create hoặc FileMode.Truncate, toàn bộ nội dung sẽ bị xóa. Cẩn thận — không chỉ sinh viên mà cả lập trình viên kỳ cựu cũng từng mất dữ liệu vì chuyện này.
Nếu bạn cố mở file không tồn tại bằng FileMode.Open hoặc FileMode.Truncate, sẽ nhận FileNotFoundException. Đừng quên kiểm tra file có thật không bằng File.Exists(path).
Với chế độ Append chỉ có thể ghi. Nếu bạn cần vừa đọc vừa ghi, hãy cân nhắc chế độ khác (OpenOrCreate + FileAccess phù hợp).
Ví dụ: Đọc và ghi
Đôi khi bạn muốn vừa đọc vừa ghi file (ví dụ mở tài khoản ngân hàng — vừa nạp tiền vừa kiểm tra số dư). Dùng FileMode.OpenOrCreate:
using System.IO;
string filePath = "balance.txt";
// Mở file để đọc và ghi; nếu chưa có thì tạo mới
var stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
// Có thể vừa đọc vừa ghi ở đây
6. Một số lưu ý hữu ích
Các chế độ mở file
graph TD
A[Yêu cầu file] --> B[Chọn chế độ]
B --> C1[CreateNew] -->|Nếu file đã có| D1[Lỗi]
C1 -->|File chưa có| E1[Tạo mới]
B --> C2[Create] -->|Luôn luôn| F1[Tạo/Ghi đè]
B --> C3[Open] -->|File đã có| G1[Mở]
C3 -->|File chưa có| D1
B --> C4[OpenOrCreate] -->|File đã có| G1
C4 -->|File chưa có| E1
B --> C5[Append] -->|File đã có| H1[Mở để ghi thêm]
C5 -->|File chưa có| E1
B --> C6[Truncate] -->|File đã có| I1[Xóa sạch]
C6 -->|File chưa có| D1
Bảng tổng hợp các chế độ mở file
| FileMode | File Đã Có | File Chưa Có | Truy cập dữ liệu | Xóa dữ liệu cũ? |
|---|---|---|---|---|
|
Exception | Tạo mới | Ghi | Không áp dụng |
|
Ghi đè | Tạo mới | Ghi | Có |
|
Mở | Exception | Đọc/ghi | Không |
|
Mở | Tạo mới | Đọc/ghi | Không |
|
Xóa sạch | Exception | Ghi | Có |
|
Mở để ghi cuối | Tạo mới | Chỉ ghi | Không (thêm vào) |
So sánh với các method cấp cao của class File
- File.WriteAllText(path, text) → bên trong dùng FileMode.Create, tức là file cũ sẽ bị ghi đè.
- File.AppendAllText(path, text) → dùng chế độ Append, mọi thứ sẽ thêm vào cuối.
- File.ReadAllText(path) → mở file để đọc (FileMode.Open).
Đôi khi chỉ cần các method này là đủ, nhưng làm việc với FileStream và chỉ định rõ chế độ sẽ cho bạn toàn quyền kiểm soát.
Lưu ý cho các hệ điều hành khác nhau
Nhiều dev không để ý rằng trên Windows, file đang được process khác mở để ghi có thể không mở lại được — cố truy cập sẽ lỗi. Vì vậy rất quan trọng là dùng đúng tham số FileShare để kiểm soát quyền truy cập đồng thời (chúng ta sẽ nói kỹ hơn ở bài sau). Ở các hệ điều hành khác, cơ chế khóa và quyền có thể khác, nhưng ý tưởng về chế độ mở file thì vẫn giống nhau.
GO TO FULL VERSION