File I/O cơ bản
Sau bài này, bạn sẽ:
- Hiểu File class để quản lý files và directories (check exists, create, delete, list)
- Đọc và ghi file văn bản với FileReader/FileWriter và BufferedReader/BufferedWriter
- Sử dụng try-with-resources để auto-close resources và tránh resource leaks
- Xử lý exceptions khi làm việc với files (IOException, FileNotFoundException)
- Áp dụng best practices: buffering, proper encoding, error handling
Bài trước: Giới thiệu I/O trong Java — Đã học về java.io vs java.nio. Bài này sẽ hướng dẫn các thao tác cơ bản với files: đọc, ghi, tạo, xóa.
File Class - Quản lý Files và Directories
Class java.io.File đại diện cho một file hoặc directory path (đường dẫn). Chú ý: File object không phải là file thật - chỉ là đường dẫn!
File object là path abstraction - KHÔNG phải file thật!
import java.io.File;
public class FilePathTrap {
public static void main(String[] args) {
// Tạo File object cho file KHÔNG TỒN TẠI
File nonexistent = new File("nonexistent.txt");
System.out.println("File created? " + nonexistent);
// Output: nonexistent.txt
// ❌ Không có lỗi! File object chỉ là PATH, không phải file thật!
// PHẢI check exists() để biết file có thật hay không
System.out.println("Does it exist? " + nonexistent.exists());
// Output: false
// Có thể gọi methods khác vẫn không lỗi!
System.out.println("Is file? " + nonexistent.isFile());
// Output: false (không phải file vì không tồn tại)
System.out.println("Length: " + nonexistent.length());
// Output: 0 (không tồn tại thì length = 0)
}
}
Kết luận: new File("...") CHỈ tạo path object, KHÔNG tạo file trên disk! Phải gọi createNewFile() hoặc FileOutputStream mới tạo file thật.
Tạo File object
import java.io.File;
public class FileExample {
public static void main(String[] args) {
// Windows: dùng \\ hoặc /
File file1 = new File("C:\\data\\file.txt");
File file2 = new File("C:/data/file.txt"); // Cũng OK
// macOS/Linux: dùng /
File file3 = new File("/Users/luatnq/data/file.txt");
// Relative path (tương đối với working directory)
File file4 = new File("data.txt");
File file5 = new File("logs/app.log");
// Tách directory và filename
File dir = new File("data");
File file6 = new File(dir, "config.txt"); // data/config.txt
System.out.println("Absolute path: " + file4.getAbsolutePath());
}
}
File.separator tự động chọn separator đúng cho OS:
- Windows:
\(backslash) - Unix/Linux/macOS:
/(forward slash)
import java.io.File;
public class FileSeparatorExample {
public static void main(String[] args) {
// Cách 1: Hard-code "/" - hoạt động trên MỌI OS!
File file1 = new File("data/config/app.properties");
// ✅ Java tự động convert "/" thành "\" trên Windows!
// Cách 2: File.separator - safer cho display/logging
String path = "data" + File.separator + "config" + File.separator + "app.properties";
File file2 = new File(path);
System.out.println("File.separator: '" + File.separator + "'");
// Windows: '\'
// Unix: '/'
// ❌ TRÁNH hard-code "\\" - chỉ chạy trên Windows!
File file3 = new File("data\\config\\app.properties");
// Lỗi trên Unix/Mac!
}
}
Kết luận: Hard-code "/" hoạt động trên mọi OS trong Java, nhưng File.separator an toàn hơn cho display/logging.
File Properties - Kiểm tra thông tin file
import java.io.File;
public class FileProperties {
public static void main(String[] args) {
File file = new File("data.txt");
// Kiểm tra tồn tại
if (file.exists()) {
System.out.println("File tồn tại!");
// Kiểm tra loại
System.out.println("Is file? " + file.isFile());
System.out.println("Is directory? " + file.isDirectory());
// Quyền truy cập
System.out.println("Can read? " + file.canRead());
System.out.println("Can write? " + file.canWrite());
System.out.println("Can execute? " + file.canExecute());
// Thông tin file
System.out.println("Name: " + file.getName());
System.out.println("Path: " + file.getPath());
System.out.println("Absolute path: " + file.getAbsolutePath());
System.out.println("Parent: " + file.getParent());
// Kích thước (bytes)
System.out.println("Size: " + file.length() + " bytes");
// Thời gian chỉnh sửa lần cuối
long lastModified = file.lastModified();
System.out.println("Last modified: " + new java.util.Date(lastModified));
// Hidden file?
System.out.println("Is hidden? " + file.isHidden());
} else {
System.out.println("File không tồn tại!");
}
}
}
File Object Lifecycle
Tạo, Xóa, Đổi tên Files và Directories
import java.io.File;
import java.io.IOException;
public class FileOperations {
public static void main(String[] args) {
try {
// ===== TẠO FILE =====
File newFile = new File("temp.txt");
if (newFile.createNewFile()) {
System.out.println("File created: " + newFile.getName());
} else {
System.out.println("File already exists.");
}
// ===== TẠO DIRECTORY =====
File dir = new File("logs");
if (dir.mkdir()) { // Tạo 1 cấp
System.out.println("Directory created: " + dir.getName());
}
File nestedDir = new File("data/config/prod");
if (nestedDir.mkdirs()) { // Tạo nhiều cấp
System.out.println("Nested directories created!");
}
// ===== ĐỔI TÊN / DI CHUYỂN =====
File oldFile = new File("temp.txt");
File renamedFile = new File("renamed.txt");
if (oldFile.renameTo(renamedFile)) {
System.out.println("File renamed successfully!");
} else {
System.out.println("Failed to rename file.");
}
// ===== XÓA FILE =====
if (renamedFile.delete()) {
System.out.println("File deleted: " + renamedFile.getName());
}
// ===== XÓA DIRECTORY (phải rỗng) =====
if (dir.delete()) {
System.out.println("Directory deleted.");
}
} catch (IOException e) {
System.err.println("I/O Error: " + e.getMessage());
}
}
}
import java.io.*;
public class FileWriterTrap {
public static void main(String[] args) throws IOException {
// Tạo file với nội dung ban đầu
try (FileWriter writer = new FileWriter("test.txt")) {
writer.write("Original content");
}
// Ghi lại vào file
try (FileWriter writer = new FileWriter("test.txt")) {
writer.write("New"); // ❌ File bị TRUNCATE!
}
// Đọc lại
try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
System.out.println(reader.readLine());
// Output: New
// ❌ "Original content" BỊ XÓA! FileWriter mặc định truncate!
}
// ĐÚNG: Dùng append mode
try (FileWriter writer = new FileWriter("test.txt", true)) { // true = append
writer.write(" - Appended");
}
try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
System.out.println(reader.readLine());
// Output: New - Appended
}
}
}
Nguy hiểm:
new FileWriter("file.txt")→ TRUNCATE file (xóa nội dung cũ!)new FileWriter("file.txt", true)→ APPEND (giữ nội dung cũ)
delete() chỉ xóa được directory rỗng. Để xóa directory có files bên trong, phải xóa toàn bộ files trước (hoặc dùng recursive delete).
Liệt kê Files trong Directory
import java.io.File;
public class ListFilesTrap {
public static void main(String[] args) {
File notDir = new File("file.txt"); // Là file, không phải directory
// ❌ listFiles() trả về NULL nếu không phải directory!
File[] files = notDir.listFiles();
// NullPointerException!
for (File file : files) { // ← Crash here!
System.out.println(file.getName());
}
// ĐÚNG: Check null trước
if (files != null) {
for (File file : files) {
System.out.println(file.getName());
}
} else {
System.out.println("Not a directory or I/O error");
}
// Hoặc check isDirectory() trước
if (notDir.isDirectory()) {
File[] dirFiles = notDir.listFiles();
// Safe: files != null (guaranteed)
}
}
}
Lưu ý:
listFiles()returns null nếu:- Path không phải directory
- I/O error
- Permission denied
list()cũng returns null (không phải empty array!)
import java.io.File;
import java.io.FilenameFilter;
public class ListFiles {
public static void main(String[] args) {
File dir = new File(".");
// ===== list() - Trả về array String =====
String[] files = dir.list();
if (files != null) { // ← Check null!
System.out.println("Files in current directory:");
for (String fileName : files) {
System.out.println(" " + fileName);
}
}
// ===== listFiles() - Trả về array File objects =====
File[] fileObjects = dir.listFiles();
if (fileObjects != null) { // ← Check null!
System.out.println("\nFiles with details:");
for (File file : fileObjects) {
String type = file.isDirectory() ? "[DIR]" : "[FILE]";
System.out.printf("%s %s (%d bytes)%n",
type, file.getName(), file.length());
}
}
// ===== Filter - Chỉ lấy .txt files =====
FilenameFilter txtFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".txt");
}
};
File[] txtFiles = dir.listFiles(txtFilter);
if (txtFiles != null) {
System.out.println("\nText files only:");
for (File file : txtFiles) {
System.out.println(" " + file.getName());
}
}
// Lambda version (Java 8+)
File[] javaFiles = dir.listFiles((d, name) -> name.endsWith(".java"));
}
}
Đọc File văn bản - FileReader & BufferedReader
FileReader - Đọc từng ký tự
import java.io.FileReader;
import java.io.IOException;
public class FileReaderExample {
public static void main(String[] args) {
// CẨN THẬN: Không tự động close nếu có exception!
FileReader reader = null;
try {
reader = new FileReader("data.txt");
int character;
while ((character = reader.read()) != -1) {
System.out.print((char) character);
}
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
} finally {
// Phải close thủ công!
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Code trên dài dòng và dễ quên close(). Hãy dùng try-with-resources (Java 7+).
BufferedReader - Đọc từng dòng (Hiệu quả hơn!)
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderExample {
public static void main(String[] args) {
// try-with-resources - Tự động close!
try (BufferedReader reader = new BufferedReader(
new FileReader("data.txt"))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
System.out.println(lineNumber + ": " + line);
lineNumber++;
}
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
}
// reader.close() được gọi tự động!
}
}
Buffered vs Unbuffered I/O Comparison
BufferedReader WHY? - Performance Deep Dive
Không có buffer: Mỗi lần read() = 1 system call → cực kỳ chậm!
FileReader.read() - Không buffer:
Application: read() → System Call → Kernel → Disk → Return 1 char
read() → System Call → Kernel → Disk → Return 1 char
read() → System Call → Kernel → Disk → Return 1 char
... (lặp lại cho mỗi ký tự!)
↑
Đọc file 1MB = 1,048,576 system calls! 💀
Có buffer: Đọc chunk lớn (8KB), lưu trong buffer, application lấy từ buffer.
BufferedReader - Có buffer:
Application: read() ─┐
read() ─┤
read() ─┤ Lấy từ buffer (trong memory - cực nhanh!)
read() ─┤
read() ─┘
└─> Khi buffer hết → System Call → Đọc 8KB tiếp
Đọc file 1MB = ~128 system calls (1MB ÷ 8KB)
Code so sánh:
import java.io.*;
public class BufferPerformance {
public static void main(String[] args) throws IOException {
String file = "large.txt"; // 10MB file
createTestFile(file, 10_000_000);
// Test 1: Không buffer - ĐỌC TỪNG KÝ TỰ
long start1 = System.currentTimeMillis();
readWithoutBuffer(file);
long time1 = System.currentTimeMillis() - start1;
// Test 2: Có buffer
long start2 = System.currentTimeMillis();
readWithBuffer(file);
long time2 = System.currentTimeMillis() - start2;
System.out.println("Without buffer: " + time1 + "ms");
System.out.println("With buffer: " + time2 + "ms");
System.out.println("Speedup: " + (time1 / (double) time2) + "x");
}
static void readWithoutBuffer(String file) throws IOException {
try (FileReader reader = new FileReader(file)) {
int ch;
while ((ch = reader.read()) != -1) {
// Mỗi read() = 1 system call!
}
}
}
static void readWithBuffer(String file) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
int ch;
while ((ch = reader.read()) != -1) {
// read() lấy từ buffer, ít system calls!
}
}
}
static void createTestFile(String file, int size) throws IOException {
try (FileWriter writer = new FileWriter(file)) {
for (int i = 0; i < size; i++) {
writer.write('A');
}
}
}
}
Output thực tế:
Without buffer: 18430ms (18 giây!)
With buffer: 127ms (0.1 giây)
Speedup: 145x
Tại sao dùng BufferedReader?
FileReader.read()gọi system call mỗi ký tự → chậm hơn hàng trăm lần!BufferedReaderđọc cả chunk (8KB mặc định) vào buffer → nhanh hơn nhiều!- Có method
readLine()tiện lợi
Đọc file với encoding UTF-8 (quan trọng cho tiếng Việt!)
import java.io.*;
import java.nio.charset.StandardCharsets;
public class EncodingTrap {
public static void main(String[] args) throws IOException {
// Tạo file với UTF-8 encoding
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("vietnamese.txt"),
StandardCharsets.UTF_8))) {
writer.write("Xin chào! Đây là tiếng Việt có dấu: áàảãạ");
}
// ❌ SAI: FileReader dùng DEFAULT encoding của OS!
System.out.println("=== FileReader (platform default) ===");
try (FileReader reader = new FileReader("vietnamese.txt")) {
int ch;
while ((ch = reader.read()) != -1) {
System.out.print((char) ch);
}
System.out.println();
// Output (on Windows): Xin chào! Đảy là tiêng Viêt có dâu
// ❌ Lỗi font! Windows default = CP1252, nhưng file là UTF-8!
}
// ✅ ĐÚNG: Chỉ định UTF-8 encoding rõ ràng
System.out.println("\n=== InputStreamReader (UTF-8) ===");
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("vietnamese.txt"),
StandardCharsets.UTF_8))) {
String line = reader.readLine();
System.out.println(line);
// Output: Xin chào! Đây là tiếng Việt có dấu: áàảãạ
// ✅ Hiển thị đúng!
}
// Platform default encoding
System.out.println("\nPlatform default encoding: " +
java.nio.charset.Charset.defaultCharset());
// Windows: windows-1252
// Linux: UTF-8
// macOS: UTF-8
}
}
Nguy hiểm:
FileReaderdùng platform default charset- Windows: CP1252 (không hỗ trợ đầy đủ Unicode!)
- Linux/macOS: UTF-8
- Code chạy đúng trên Linux nhưng SAI trên Windows!
- LUÔN chỉ định charset UTF-8 khi làm việc với tiếng Việt!
import java.io.*;
import java.nio.charset.StandardCharsets;
public class ReadVietnameseFile {
public static void main(String[] args) {
// ĐÚNG: Chỉ định UTF-8 encoding
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("vietnamese.txt"),
StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line); // Hiển thị đúng tiếng Việt
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileReader = Nguy hiểm (platform-dependent encoding) InputStreamReader + StandardCharsets.UTF_8 = An toàn (explicit encoding)
Luôn dùng UTF-8 cho file tiếng Việt!
Ghi File văn bản - FileWriter & BufferedWriter
FileWriter - Ghi từng ký tự
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterExample {
public static void main(String[] args) {
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello, World!\n");
writer.write("This is line 2.\n");
// Ghi từng ký tự
writer.write('A');
writer.write('\n');
// Ghi char array
char[] chars = {'J', 'a', 'v', 'a'};
writer.write(chars);
} catch (IOException e) {
System.err.println("Error writing file: " + e.getMessage());
}
}
}
BufferedWriter - Ghi hiệu quả hơn
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterExample {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(
new FileWriter("output.txt"))) {
writer.write("Line 1");
writer.newLine(); // Platform-independent newline (\n hoặc \r\n)
writer.write("Line 2");
writer.newLine();
// Ghi nhiều lines
for (int i = 1; i <= 100; i++) {
writer.write("Line " + i);
writer.newLine();
}
// flush(): đẩy dữ liệu từ buffer ra file ngay lập tức
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Append vào file (không ghi đè)
import java.io.FileWriter;
import java.io.IOException;
public class AppendToFile {
public static void main(String[] args) {
// Tham số thứ 2 = true → append mode
try (FileWriter writer = new FileWriter("log.txt", true)) {
writer.write("[" + new java.util.Date() + "] Application started\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Ghi file với encoding UTF-8
import java.io.*;
import java.nio.charset.StandardCharsets;
public class WriteVietnameseFile {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("vietnamese.txt"),
StandardCharsets.UTF_8))) {
writer.write("Xin chào! Đây là tiếng Việt.");
writer.newLine();
writer.write("Java I/O hỗ trợ Unicode tốt.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
try-with-resources - Best Practice & Deep Dive
Trước Java 7 (rất tệ!):
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
// ... đọc file
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close(); // Dễ quên!
} catch (IOException e) {
e.printStackTrace();
}
}
}
Java 7+ với try-with-resources (tốt!):
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
// ... đọc file
} catch (IOException e) {
e.printStackTrace();
}
// reader.close() tự động được gọi!
Multiple resources:
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line.toUpperCase());
writer.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
// Cả reader và writer đều được close tự động!
try-with-resources Deep Dive: Suppressed Exceptions
Nếu cả try body VÀ close() đều throw exception, exception nào được throw ra?
import java.io.*;
public class SuppressedExceptionExample {
public static void main(String[] args) {
try {
try (FailingResource resource = new FailingResource()) {
resource.doWork();
// Throws IOException("Work failed")
}
// close() cũng throws IOException("Close failed")
} catch (IOException e) {
System.out.println("Main exception: " + e.getMessage());
// Output: Work failed
// Suppressed exceptions (từ close())
Throwable[] suppressed = e.getSuppressed();
for (Throwable t : suppressed) {
System.out.println("Suppressed: " + t.getMessage());
// Output: Close failed
}
}
}
}
class FailingResource implements AutoCloseable {
void doWork() throws IOException {
throw new IOException("Work failed");
}
@Override
public void close() throws IOException {
throw new IOException("Close failed");
}
}
Kết quả:
Main exception: Work failed
Suppressed: Close failed
Quy tắc:
- Exception từ
trybody → main exception - Exception từ
close()→ suppressed exception - Access qua
getSuppressed()
import java.io.*;
public class CloseFl ushTrap {
public static void main(String[] args) {
try {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write("Line 1\n");
writer.write("Line 2\n");
// Không gọi flush() manually
// Simulate error
if (true) {
throw new IOException("Simulated error");
}
}
// close() TỰ ĐỘNG gọi flush()...
// NHƯNG nếu có exception → không chạy được đến close()!
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}
// Kiểm tra file
try (BufferedReader reader = new BufferedReader(new FileReader("output.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// Output: (empty hoặc không đầy đủ)
// ❌ Data loss! Không flush kịp!
} catch (IOException e) {
e.printStackTrace();
}
}
}
Nguy hiểm:
close()callsflush()before closing- NHƯNG nếu exception xảy ra TRƯỚC close() → data trong buffer bị mất!
- Best practice: Gọi
flush()explicitly sau mỗi lần ghi quan trọng
try-with-resources Suppressed Exceptions Flow
Bất kỳ class nào implement AutoCloseable đều có thể dùng trong try-with-resources. Tất cả I/O classes (Reader, Writer, InputStream, OutputStream) đều implement interface này.
Đọc/Ghi file đơn giản với Scanner và PrintWriter
Scanner - Đọc file theo tokens
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ScannerExample {
public static void main(String[] args) {
try (Scanner scanner = new Scanner(new File("data.txt"))) {
// Đọc từng dòng
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
} catch (FileNotFoundException e) {
System.err.println("File not found!");
}
}
// Đọc file CSV
public static void readCSV() {
try (Scanner scanner = new Scanner(new File("data.csv"))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
String[] fields = line.split(",");
for (String field : fields) {
System.out.print(field.trim() + " | ");
}
System.out.println();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
PrintWriter - Ghi file với formatting
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class PrintWriterExample {
public static void main(String[] args) {
try (PrintWriter writer = new PrintWriter(new FileWriter("report.txt"))) {
// printf-style formatting
writer.printf("Name: %s%n", "Nguyễn Văn A");
writer.printf("Age: %d%n", 25);
writer.printf("Score: %.2f%n", 8.75);
// println
writer.println("===== Report End =====");
// Không ném IOException - check error bằng checkError()
if (writer.checkError()) {
System.err.println("Error occurred while writing!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Ví dụ thực tế
Ví dụ 1: Đọc Config File (Properties)
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class ConfigReader {
public static void main(String[] args) {
Properties config = new Properties();
try (FileInputStream input = new FileInputStream("app.properties")) {
config.load(input);
String host = config.getProperty("database.host");
String port = config.getProperty("database.port");
String user = config.getProperty("database.user");
System.out.println("DB Host: " + host);
System.out.println("DB Port: " + port);
System.out.println("DB User: " + user);
} catch (IOException e) {
System.err.println("Cannot load config: " + e.getMessage());
}
}
}
File app.properties:
database.host=localhost
database.port=5432
database.user=admin
database.password=secret123
Ví dụ 2: Ghi Log File
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class SimpleLogger {
private static final String LOG_FILE = "application.log";
private static final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void log(String level, String message) {
try (BufferedWriter writer = new BufferedWriter(
new FileWriter(LOG_FILE, true))) { // append mode
String timestamp = LocalDateTime.now().format(formatter);
String logEntry = String.format("[%s] [%s] %s",
timestamp, level, message);
writer.write(logEntry);
writer.newLine();
} catch (IOException e) {
System.err.println("Failed to write log: " + e.getMessage());
}
}
public static void main(String[] args) {
log("INFO", "Application started");
log("DEBUG", "Loading configuration...");
log("ERROR", "Database connection failed");
log("INFO", "Application stopped");
}
}
Output trong application.log:
[2026-02-08 14:30:15] [INFO] Application started
[2026-02-08 14:30:15] [DEBUG] Loading configuration...
[2026-02-08 14:30:16] [ERROR] Database connection failed
[2026-02-08 14:30:20] [INFO] Application stopped
Ví dụ 3: Copy File (text)
import java.io.*;
public class FileCopy {
public static void copyFile(String source, String destination) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(source));
BufferedWriter writer = new BufferedWriter(new FileWriter(destination))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
System.out.println("File copied successfully!");
}
}
public static void main(String[] args) {
try {
copyFile("source.txt", "backup.txt");
} catch (IOException e) {
System.err.println("Copy failed: " + e.getMessage());
}
}
}
File I/O Class Hierarchy
Giải thích hierarchy:
- File → Path abstraction (không đọc/ghi)
- FileInputStream/FileOutputStream → Đọc/ghi bytes, không buffer
- FileReader/FileWriter → Đọc/ghi chars, platform encoding (❌ nguy hiểm!)
- InputStreamReader/OutputStreamWriter → Bridge byte ↔ char, explicit charset (✅ an toàn!)
- BufferedInputStream/BufferedOutputStream → Thêm buffering cho byte streams
- BufferedReader/BufferedWriter → Thêm buffering cho char streams +
readLine()/newLine() - DataInputStream/DataOutputStream → Đọc/ghi primitive types (int, double, etc.)
Recommendation chain:
// ✅ Best practice cho text files (tiếng Việt):
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("file.txt"),
StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("file.txt"),
StandardCharsets.UTF_8));
// ❌ Tránh:
FileReader reader = new FileReader("file.txt"); // Platform encoding!
Tổng kết
Các điểm quan trọng
- File class: Đại diện cho path, không phải file thật
- FileReader/FileWriter: Đọc/ghi text files, mặc định encoding
- BufferedReader/BufferedWriter: Đọc/ghi hiệu quả hơn với buffer
- try-with-resources: Luôn dùng để tự động close resources
- UTF-8 encoding: Luôn chỉ định rõ ràng cho tiếng Việt
- Scanner/PrintWriter: Tiện lợi cho input/output đơn giản
Best Practices
✅ Luôn dùng try-with-resources ✅ Chỉ định encoding UTF-8 cho file tiếng Việt ✅ Dùng BufferedReader/Writer thay vì FileReader/Writer ✅ Check file.exists() trước khi đọc ✅ Handle exceptions properly
❌ Không quên close() resources ❌ Không hard-code paths (dùng File.separator) ❌ Không bỏ qua exceptions
Bài tập thực hành:
-
Viết chương trình đọc file text và đếm:
- Số dòng
- Số từ
- Số ký tự
- Từ xuất hiện nhiều nhất
-
Tạo chương trình quản lý danh bạ (phonebook):
- Lưu contacts vào file CSV
- Thêm, xóa, tìm kiếm contacts
- Đọc và ghi từ file
-
Implement simple backup utility:
- Copy tất cả .txt files từ directory này sang directory khác
- Thêm timestamp vào tên file backup