CodeGym /Các khóa học /JAVA 25 SELF /Thiết lập mã hóa khi đọc/ghi tệp

Thiết lập mã hóa khi đọc/ghi tệp

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

1. Giới thiệu

Hãy thẳng thắn: nếu bạn từng thấy thay cho văn bản tiếng Nga là thứ gì đó như Привет, thì bạn đã là nạn nhân của việc mã hóa sai. Điều này xảy ra khi tệp được ghi bằng một bộ mã nhưng lại được đọc bằng bộ mã khác. Ví dụ, tệp được lưu dưới dạng UTF-8 nhưng lại được đọc như Windows-1251, hoặc ngược lại.

Mặc định, Java sử dụng bộ mã hệ thống, bạn có thể kiểm tra như sau:

System.out.println(System.getProperty("file.encoding"));

Trên một máy có thể là UTF-8, trên máy khác — Windows-1251, và ở nơi khác nữa — ISO-8859-1. Vì vậy, luôn tốt hơn là chỉ định mã hóa một cách rõ ràng. Điều này đặc biệt quan trọng khi bạn làm việc với dữ liệu đa ngôn ngữ, khi các tệp sẽ được dùng trên nhiều máy hoặc mở bằng các chương trình khác, hoặc khi bạn cần mã của mình hoạt động nhất quán trong mọi môi trường, chứ không chỉ trên máy của bạn.

Lớp Charset: người bạn trong thế giới mã hóa

Trong Java, để làm việc với mã hóa, dùng lớp java.nio.charset.Charset. Nó cho phép chỉ định bộ mã bằng tên (ví dụ, "UTF-8") hoặc dùng các hằng số chuẩn (StandardCharsets.UTF_8).

Ví dụ về các bộ mã chuẩn:

Bộ mã Hằng số Java
UTF-8
StandardCharsets.UTF_8
UTF-16
StandardCharsets.UTF_16
ISO-8859-1
StandardCharsets.ISO_8859_1
Windows-1251
Charset.forName("Windows-1251")

Nên ưu tiên dùng hằng số: ít khả năng sai tên và sẽ không gặp UnsupportedCharsetException.

2. Đọc tệp với mã hóa chỉ định

Cách cũ:

import java.io.*;
import java.nio.charset.StandardCharsets;

BufferedReader reader = new BufferedReader(
    new InputStreamReader(
        new FileInputStream("example.txt"),
        StandardCharsets.UTF_8 // <-- Chỉ định rõ ràng bộ mã
    )
);

Cách hiện đại:

import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.BufferedReader;

BufferedReader reader = Files.newBufferedReader(
    Paths.get("example.txt"),
    StandardCharsets.UTF_8 // <-- Chỉ định rõ ràng bộ mã
);

Khuyến nghị dùng cách thứ hai — nó ngắn gọn hơn, an toàn hơn và dễ kết hợp với try-with-resources.

Ví dụ: đọc một dòng từ tệp

try (BufferedReader reader = Files.newBufferedReader(
        Paths.get("hello.txt"),
        StandardCharsets.UTF_8)) {
    String line = reader.readLine();
    System.out.println("Đã đọc: " + line);
}

Vì sao điều này quan trọng: Nếu tệp được lưu bằng UTF-8 mà bạn đọc như Windows-1251, các ký tự Cyrillic sẽ bị méo. Nếu chỉ định đúng bộ mã, văn bản sẽ được đọc chính xác trên mọi hệ điều hành.

3. Ghi tệp với mã hóa chỉ định

Cách cũ:

import java.io.*;
import java.nio.charset.StandardCharsets;

BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(
        new FileOutputStream("example.txt"),
        StandardCharsets.UTF_8 // <-- Chỉ định rõ ràng bộ mã
    )
);

Cách hiện đại:

import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.BufferedWriter;

BufferedWriter writer = Files.newBufferedWriter(
    Paths.get("example.txt"),
    StandardCharsets.UTF_8 // <-- Chỉ định rõ ràng bộ mã
);

Ví dụ: ghi một dòng vào tệp

try (BufferedWriter writer = Files.newBufferedWriter(
        Paths.get("hello.txt"),
        StandardCharsets.UTF_8)) {
    writer.write("Xin chào, thế giới!");
}

Kết quả: Tệp sẽ được lưu bằng UTF-8, và có thể mở đúng trong bất kỳ trình soạn thảo nào hỗ trợ UTF-8.

4. Những điểm hữu ích

Cách biết các bộ mã được hỗ trợ

import java.nio.charset.Charset;

public class ListCharsets {
    public static void main(String[] args) {
        System.out.println("Các bộ mã khả dụng:");
        Charset.availableCharsets().forEach((name, charset) -> System.out.println(name));
    }
}

Mẹo: Nếu bạn dùng một bộ mã “lạ” (ví dụ cho chữ tượng hình Trung Hoa cổ hoặc emoji “ngoài hành tinh”), hãy kiểm tra xem JVM của bạn có hỗ trợ nó không.

Sử dụng try-with-resources: đừng quên đóng luồng

Khi làm việc với tệp, việc đóng luồng là quan trọng để tránh rò rỉ tài nguyên. Mã Java hiện đại sử dụng cấu trúc try-with-resources:

try (BufferedReader reader = Files.newBufferedReader(path, charset)) {
    // Làm việc với tệp
}

Luồng sẽ được đóng tự động, ngay cả khi xảy ra lỗi.

Khuyến nghị

  • Nên luôn chỉ định rõ ràng mã hóa khi đọc và ghi tệp, ngay cả khi bạn nghĩ rằng “mặc định là ổn”.
  • Dùng UTF-8 cho các tệp mới — đây là tiêu chuẩn de facto, đặc biệt nếu bạn làm việc với web, JSON, XML, hoặc muốn tệp của mình có thể đọc ở mọi nơi.
  • Với các tệp cũ (ví dụ, dữ liệu xuất từ 1C, các cơ sở dữ liệu cũ, CSV từ Windows) hãy dùng đúng bộ mã mà chúng đã được lưu (ví dụ, Windows-1251, ISO-8859-1).
  • Không sử dụng các lớp đã lỗi thời, nơi không thể chỉ định mã hóa rõ ràng: FileReader/FileWriter. Thay vào đó, dùng InputStreamReader/OutputStreamWriter với mã hóa rõ ràng hoặc các phương thức từ Files.
  • Với tệp lớn hãy dùng bộ đệm (BufferedReader/BufferedWriter) để không ngốn hết bộ nhớ.

5. Các lỗi thường gặp khi làm việc với mã hóa

Lỗi #1: Không chỉ định mã hóa khi đọc/ghi tệp.
Nếu không chỉ định, Java sẽ dùng bộ mã hệ thống mặc định ("file.encoding"). Trên máy bạn mọi thứ có thể vẫn hoạt động, nhưng trên máy đồng nghiệp — “chữ loằng ngoằng”.

Lỗi #2: Không khớp mã hóa giữa lúc ghi và lúc đọc.
Tệp được ghi bằng một bộ mã nhưng lại đọc bằng bộ mã khác. Ví dụ, tệp được ghi bằng UTF-8 nhưng đọc như Windows-1251 — các ký tự Cyrillic sẽ bị méo.

Lỗi #3: Sử dụng các lớp đã lỗi thời FileReader/FileWriter.
Các lớp này không cho phép chỉ định mã hóa một cách rõ ràng — không nên dùng. Hãy dùng InputStreamReader/OutputStreamWriter với mã hóa chỉ định hoặc các phương thức từ Files.

Lỗi #4: Sai tên bộ mã.
Ví dụ, viết "utf8" thay vì "UTF-8" hoặc "win1251" thay vì "Windows-1251". Java sẽ ném UnsupportedCharsetException.

Lỗi #5: Không đóng luồng — tệp không được ghi đầy đủ.
Nếu không dùng try-with-resources hoặc không đóng luồng một cách rõ ràng, một phần dữ liệu có thể không được ghi xuống đĩa.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION