Sự cố mã hóa Java FileReader


130

Tôi đã cố gắng sử dụng java.io.FileReader để đọc một số tệp văn bản và chuyển đổi chúng thành một chuỗi, nhưng tôi thấy kết quả được mã hóa sai và hoàn toàn không thể đọc được.

Đây là môi trường của tôi:

  • Windows 2003, hệ điều hành mã hóa: CP1252

  • Java 5.0

Các tệp của tôi được mã hóa UTF-8 hoặc mã hóa CP1252 và một số trong số chúng (các tệp được mã hóa UTF-8) có thể chứa các ký tự tiếng Trung (không phải tiếng Latinh).

Tôi sử dụng mã sau đây để thực hiện công việc của mình:

   private static String readFileAsString(String filePath)
    throws java.io.IOException{
        StringBuffer fileData = new StringBuffer(1000);
        FileReader reader = new FileReader(filePath);
        //System.out.println(reader.getEncoding());
        BufferedReader reader = new BufferedReader(reader);
        char[] buf = new char[1024];
        int numRead=0;
        while((numRead=reader.read(buf)) != -1){
            String readData = String.valueOf(buf, 0, numRead);
            fileData.append(readData);
            buf = new char[1024];
        }
        reader.close();
        return fileData.toString();
    }

Các mã trên không hoạt động. Tôi thấy mã hóa của FileReader là CP1252 ngay cả khi văn bản được mã hóa UTF-8. Nhưng JavaDoc của java.io.FileReader nói rằng:

Các hàm tạo của lớp này giả định rằng mã hóa ký tự mặc định và kích thước bộ đệm byte mặc định là phù hợp.

Điều này có nghĩa là tôi không bắt buộc phải tự mình thiết lập mã hóa ký tự nếu tôi đang sử dụng FileReader? Nhưng hiện tại tôi đã nhận được dữ liệu được mã hóa sai, cách chính xác để xử lý tình huống của tôi là gì? Cảm ơn.


Bạn cũng nên thả String.valueOf () bên trong vòng lặp và sử dụng StringBuffer.append (char [], int, int) trực tiếp. Điều này tiết kiệm rất nhiều bản sao của char []. Đồng thời thay thế StringBuffer bằng StringBuilder. Không ai trong số này là về câu hỏi của bạn, 'mặc dù.
Joachim Sauer

1
Tôi ghét phải nói điều đó, nhưng bạn đã đọc JavaDoc ngay sau khi bạn dán chưa? Bạn có biết, phần có nội dung "Để tự xác định các giá trị này, hãy xây dựng InputStreamReader trên FileInputStream."?
Powerlord

Cảm ơn bình luận của bạn, thực sự tôi đã đọc JavaDoc, nhưng điều tôi không chắc là liệu tôi có nên tự chỉ định các giá trị này hay không và chuyển sang "xây dựng InputStreamReader trên FileInputStream".
nybon

Có, nếu bạn biết tệp nằm trong một cái gì đó ngoài mã hóa mặc định của nền tảng, bạn phải thông báo cho InputStreamReader nên sử dụng tệp nào.
Alan Moore

Câu trả lời:


248

Có, bạn cần chỉ định mã hóa của tệp bạn muốn đọc.

Vâng, điều này có nghĩa là bạn phải biết mã hóa tệp bạn muốn đọc.

Không, không có cách nào chung để đoán mã hóa của bất kỳ tệp "văn bản thuần túy" nào.

Các hàm tạo một đối sốFileReader luôn sử dụng mã hóa mặc định của nền tảng thường là một ý tưởng tồi .

Vì Java 11 FileReadercũng đã có được các hàm tạo chấp nhận mã hóa: new FileReader(file, charset)new FileReader(fileName, charset).

Trong các phiên bản trước của java, bạn cần sử dụng .new InputStreamReader(new FileInputStream(pathToFile), <encoding>)


1
InputStream là = new FileInputStream (tên tệp); Ở đây tôi nhận được tệp lỗi không tìm thấy lỗi với tên tệp tiếng Nga
Bhanu Sharma

3
+1 cho đề xuất sử dụng InputStreamReader, tuy nhiên việc sử dụng các liên kết trong các khối mã khiến việc sao chép và dán mã trở nên khó khăn, nếu điều này có thể thay đổi, thx
Ferrybig

1
Nó sẽ là "UTF-8" hoặc "UTF8" trong bảng mã. Theo tài liệu tham khảo Java SE về mã hóa , vì InputStreamReaderlà một java.iolớp, nó sẽ là "UTF8"?
NobleUplift

9
@NobleUplift: đặt cược an toàn nhất là StandardCharsets.UTF_8, không có cơ hội nhầm lẫn ở đó ;-) Nhưng vâng, nếu bạn đi với chuỗi "UTF8"sẽ đúng (mặc dù tôi dường như nhớ rằng nó sẽ chấp nhận cả hai cách).
Joachim Sauer

1
@JoachimSauer Trên thực tế, đây là một trong những mục đích của Byte Order Mark, cùng với .. à .. thiết lập thứ tự byte! :) Như vậy tôi thấy thật kỳ lạ khi FileReader của Java không thể tự động phát hiện UTF-16 có BOM như vậy ... Thực tế tôi đã từng viết một UnicodeFileReaderthứ thực hiện chính xác điều đó. Thật không may là nguồn đóng, nhưng Google có UnicodeReader rất giống nhau.
Stijn de Witt

79

FileReader sử dụng mã hóa mặc định nền tảng của Java, tùy thuộc vào cài đặt hệ thống của máy tính mà nó đang chạy và thường là mã hóa phổ biến nhất trong số những người dùng ở miền địa phương đó.

Nếu "dự đoán tốt nhất" này không chính xác thì bạn phải xác định rõ ràng mã hóa. Thật không may, FileReaderkhông cho phép điều này (giám sát chính trong API). Thay vào đó, bạn phải sử dụng new InputStreamReader(new FileInputStream(filePath), encoding)và lý tưởng là lấy mã hóa từ siêu dữ liệu về tệp.


24
"giám sát lớn trong API" - cảm ơn vì lời giải thích này - tôi đã tự hỏi tại sao tôi không thể tìm thấy nhà xây dựng mà tôi đã theo đuổi! Chúc mừng John
monojohnny

@Bhanu Sharma: đó là một vấn đề mã hóa ở một cấp độ khác, hãy kiểm tra xem bạn lấy tên tệp từ đâu và nếu nó được mã hóa thì trình biên dịch sử dụng mã hóa nào.
Michael Borgwardt

1
@BhanuSharma: vấn đề mã hóa tên tệp không liên quan gì đến câu hỏi này. Xem một trong số nhiều phiên bản hiện tại tại sao tên tệp Unicode không hoạt động trong các câu hỏi Java Java. Spoiler: các API java.io như FileReader sử dụng các lệnh gọi hệ thống tệp thư viện chuẩn C, không thể hỗ trợ Unicode trên Windows; thay vào đó hãy xem xét sử dụng java.nio.
bobince

1
" FileReadersử dụng mã hóa mặc định nền tảng của Java, phụ thuộc vào cài đặt hệ thống của máy tính mà nó đang chạy và thường là mã hóa phổ biến nhất trong số những người dùng ở miền địa phương đó." Tôi sẽ không nói như vậy. Ít nhất là Windows. Đối với một số lý do kỹ thuật / lịch sử kỳ lạ, JVM bỏ qua thực tế rằng Unicode là mã hóa được đề xuất trên Windows cho 'tất cả các ứng dụng mới' và thay vào đó luôn hoạt động như thể mã hóa kế thừa được định cấu hình là dự phòng cho các ứng dụng cũ là 'mặc định nền tảng'.
Stijn de Witt

6
Tôi thậm chí sẽ đi xa hơn khi nói rằng nếu ứng dụng Java của bạn không chỉ định rõ ràng các mã hóa mỗi khi nó đọc hoặc ghi vào các tệp / luồng / tài nguyên, thì nó sẽ bị hỏng , vì nó không thể hoạt động một cách đáng tin cậy sau đó.
Stijn de Witt

8

Vì Java 11, bạn có thể sử dụng:

public FileReader(String fileName, Charset charset) throws IOException;

6

Đối với tài liệu Java 7+, bạn có thể sử dụng tài liệu này:

BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8);

Dưới đây là tất cả các tài liệu Charsets

Ví dụ: nếu tệp của bạn nằm trong CP1252, hãy sử dụng phương pháp này

Charset.forName("windows-1252");

Đây là các tên chính tắc khác cho mã hóa Java cho cả tài liệu IO và NIO

Nếu bạn không biết với mã hóa chính xác mà bạn có trong một tệp, bạn có thể sử dụng một số lib của bên thứ ba như công cụ này của Google, công cụ này hoạt động khá gọn gàng.


1

FileInputStream với InputStreamReader tốt hơn so với sử dụng trực tiếp FileReader, vì cái sau không cho phép bạn chỉ định bảng mã hóa.

Dưới đây là một ví dụ sử dụng BufferedReader, FileInputStream và InputStreamReader cùng nhau, để bạn có thể đọc các dòng từ một tệp.

List<String> words = new ArrayList<>();
List<String> meanings = new ArrayList<>();
public void readAll( ) throws IOException{
    String fileName = "College_Grade4.txt";
    String charset = "UTF-8";
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(
            new FileInputStream(fileName), charset)); 

    String line; 
    while ((line = reader.readLine()) != null) { 
        line = line.trim();
        if( line.length() == 0 ) continue;
        int idx = line.indexOf("\t");
        words.add( line.substring(0, idx ));
        meanings.add( line.substring(idx+1));
    } 
    reader.close();
}

0

Đối với ngôn ngữ Latinh khác, ví dụ như tiếng Cyrillic, bạn có thể sử dụng một cái gì đó như thế này:

FileReader fr = new FileReader("src/text.txt", StandardCharsets.UTF_8);

và chắc chắn rằng .txttệp của bạn được lưu với UTF-8định ANSIdạng (nhưng không phải là mặc định ). Chúc mừng!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.