Viết tệp trong UTF-8 bằng FileWriter (Java)?


82

Tuy nhiên, tôi có mã sau, tôi muốn nó viết dưới dạng tệp UTF-8 để xử lý các ký tự nước ngoài. Có một cách để làm điều này, một số cần phải có một tham số?

Tôi thực sự đánh giá cao sự giúp đỡ của bạn với điều này. Cảm ơn.

try {
  BufferedReader reader = new BufferedReader(new FileReader("C:/Users/Jess/My Documents/actresses.list"));
  writer = new BufferedWriter(new FileWriter("C:/Users/Jess/My Documents/actressesFormatted.csv"));
  while( (line = reader.readLine()) != null) {
    //If the line starts with a tab then we just want to add a movie
    //using the current actor's name.
    if(line.length() == 0)
      continue;
    else if(line.charAt(0) == '\t') {
      readMovieLine2(0, line, surname.toString(), forename.toString());
    } //Else we've reached a new actor
    else {
      readActorName(line);
    }
  }
} catch (IOException e) {
  e.printStackTrace();
}

Câu trả lời:


77

Trình tạo mã hóa an toàn

Để Java thông báo chính xác cho bạn về các lỗi mã hóa là một việc khó. Bạn phải sử dụng chi tiết nhất và, than ôi, được sử dụng ít nhất trong số bốn cấu trúc thay thế cho mỗi cấu trúc InputStreamReaderOutputStreamWriterđể nhận được một ngoại lệ thích hợp về trục trặc mã hóa.

Đối với I / O tệp, hãy luôn đảm bảo luôn sử dụng làm đối số thứ hai cho cả hai OutputStreamWriterInputStreamReaderđối số bộ mã hóa ưa thích:

  Charset.forName("UTF-8").newEncoder()

Có những khả năng khác thậm chí còn kỳ diệu hơn, nhưng không có khả năng nào trong ba khả năng đơn giản hơn phù hợp với việc xử lý ngoại lệ. Những điều này làm:

 OutputStreamWriter char_output = new OutputStreamWriter(
     new FileOutputStream("some_output.utf8"),
     Charset.forName("UTF-8").newEncoder() 
 );

 InputStreamReader char_input = new InputStreamReader(
     new FileInputStream("some_input.utf8"),
     Charset.forName("UTF-8").newDecoder() 
 );

Đối với việc chạy với

 $ java -Dfile.encoding=utf8 SomeTrulyRemarkablyLongcLassNameGoeShere

Vấn đề là điều đó sẽ không sử dụng biểu mẫu đối số bộ mã hóa đầy đủ cho các luồng ký tự, và vì vậy bạn sẽ lại bỏ lỡ các vấn đề mã hóa.

Ví dụ dài hơn

Đây là một ví dụ dài hơn, ví dụ này quản lý một quá trình thay vì một tệp, trong đó chúng tôi quảng bá hai luồng byte đầu vào khác nhau và một luồng byte đầu ra tất cả thành luồng ký tự UTF-8 với xử lý ngoại lệ đầy đủ :

 // this runs a perl script with UTF-8 STD{IN,OUT,ERR} streams
 Process
 slave_process = Runtime.getRuntime().exec("perl -CS script args");

 // fetch his stdin byte stream...
 OutputStream
 __bytes_into_his_stdin  = slave_process.getOutputStream();

 // and make a character stream with exceptions on encoding errors
 OutputStreamWriter
   chars_into_his_stdin  = new OutputStreamWriter(
                             __bytes_into_his_stdin,
         /* DO NOT OMIT! */  Charset.forName("UTF-8").newEncoder()
                         );

 // fetch his stdout byte stream...
 InputStream
 __bytes_from_his_stdout = slave_process.getInputStream();

 // and make a character stream with exceptions on encoding errors
 InputStreamReader
   chars_from_his_stdout = new InputStreamReader(
                             __bytes_from_his_stdout,
         /* DO NOT OMIT! */  Charset.forName("UTF-8").newDecoder()
                         );

// fetch his stderr byte stream...
 InputStream
 __bytes_from_his_stderr = slave_process.getErrorStream();

 // and make a character stream with exceptions on encoding errors
 InputStreamReader
   chars_from_his_stderr = new InputStreamReader(
                             __bytes_from_his_stderr,
         /* DO NOT OMIT! */  Charset.forName("UTF-8").newDecoder()
                         );

Bây giờ bạn có ba dòng nhân vật mà tất cả tăng ngoại lệ về mã hóa lỗi, tương ứng gọi là chars_into_his_stdin, chars_from_his_stdoutchars_from_his_stderr.

Điều này chỉ phức tạp hơn một chút so với những gì bạn cần cho vấn đề của mình, giải pháp mà tôi đã đưa ra trong nửa đầu của câu trả lời này. Điểm mấu chốt là đây là cách duy nhất để phát hiện lỗi mã hóa.

Chỉ cần đừng khiến tôi bắt đầu về PrintStreamcác ngoại lệ ăn uống.


1
Câu trả lời tuyệt vời, nhưng tôi nghĩ rằng có một lỗi nhỏ với nó - InputStreamReader char_input = new InputStreamWriternên đọc: InputStreamReader char_input = new InputStreamReader và hàm InputStreamReadertạo nhận a CharsetDecoder, không phải a CharsetEncoder.
Mark Rhodes,

Nhưng đây có phải là vấn đề thực sự không, UTF-8 không thể đại diện cho những gì, tôi nghĩ nó có thể mã hóa bất cứ thứ gì.
Paul Taylor

Nếu bạn muốn khiếu nại về Streams ăn ngoại lệ, hãy thử CipherInputStream, mà loại bỏ BadPaddingException's, ngay cả khi họ được tạo ra bởi một dòng mật mã xác nhận :(
Maarten Bodewes

Tôi đã tìm thấy một lỗi nhỏ trong mã của bạn: "Charset.forName (" UTF-8 "). NewEncoder ()" cho "InputStreamReader" phải là "Charset.forName (" UTF-8 "). NewDecoder ()". Vì vậy, "decoder" thay vì "encoder". Nhưng dù sao, cảm ơn vì câu trả lời hay và +1 này. :)
codepleb

2
(Toàn bộ hệ thống Java IO luôn là một mớ hỗn độn. Nên được làm lại hoàn toàn giống như ngày làm lại của Joda Time.)
Tuntable vào

56

Ditch FileWriterFileReader, chính xác là vô dụng vì chúng không cho phép bạn chỉ định mã hóa. Thay vào đó, hãy sử dụng

new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)

new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);


12
Nếu bạn không sử dụng Charset.forName("UTF-8").newDecoder()đối số dài dòng (hoặc một số cấu trúc huyền ảo) thay vì chỉ "UTF-8", bạn sẽ không được thông báo chính xác về lỗi mã hóa (đọc: ngoại lệ sẽ bị loại bỏ và nó sẽ ẩn một cách bí ẩn các lỗi mã hóa).
tchrist

3
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8 )
Abdull

46

Bạn cần sử dụng OutputStreamWriterlớp làm tham số người viết cho của bạn BufferedWriter. Nó chấp nhận một mã hóa. Đánh giá javadocs cho nó.

Hơi giống như thế này:

BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
    new FileOutputStream("jedis.txt"), "UTF-8"
));

Hoặc bạn có thể đặt mã hóa hệ thống hiện tại với thuộc tính hệ thống file.encodingthành UTF-8.

java -Dfile.encoding=UTF-8 com.jediacademy.Runner arg1 arg2 ...

Bạn cũng có thể đặt nó làm thuộc tính hệ thống trong thời gian chạy System.setProperty(...)nếu bạn chỉ cần nó cho tệp cụ thể này, nhưng trong trường hợp như thế này, tôi nghĩ tôi sẽ thích OutputStreamWriter.

Bằng cách đặt thuộc tính hệ thống, bạn có thể sử dụng FileWritervà mong đợi rằng nó sẽ sử dụng UTF-8 làm mã hóa mặc định cho các tệp của bạn. Trong trường hợp này cho tất cả các tệp mà bạn đọc và ghi.

BIÊN TẬP

  • Bắt đầu từ API 19, bạn có thể thay thế Chuỗi "UTF-8" bằng StandardCharsets.UTF_8

  • Như được đề xuất trong các nhận xét bên dưới của tchrist , nếu bạn định phát hiện lỗi mã hóa trong tệp của mình, bạn sẽ buộc phải sử dụng OutputStreamWriterphương pháp này và sử dụng hàm tạo nhận bộ mã hóa ký tự.

    Hơi giống

    CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
    encoder.onMalformedInput(CodingErrorAction.REPORT);
    encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("jedis.txt"),encoder));
    

    Bạn có thể chọn giữa các hành động IGNORE | REPLACE | REPORT

Ngoài ra, câu hỏi này đã được trả lời ở đây .


Như thế là không đủ. Bạn cũng cần một InputStreamReader(InputStream in, CharsetDecoder dec), sao cho đối số cuối cùng là Charset.forName("UTF-8").newDecoder().
tchrist

1
Lỗi mã hóa đầu vào sẽ bị loại bỏ một cách âm thầm nếu bạn làm điều đó.
tchrist

Không cần bộ mã hóa. Hàm tạo chấp nhận một Chuỗi, một Bộ mã hoặc một Bộ mã hóa trong cả hai lớp Đầu vào / Đầu ra. Không chắc bạn có ý gì về nhận xét của bạn. Bạn có thể nói rõ hơn được không?
Edwin Dalorzo

3
@edalorzo Nếu bạn kiểm tra bốn khác nhau {In,Out}putStream{Reader,Writer}constructers trên dữ liệu có sai sót, bạn sẽ khám phá ra rằng ba trong số họ che giấu tất cả các trường hợp ngoại lệnên phát sinh từ lỗi mã hóa, và chỉ có hình thức thứ tư một cách chính xác cung cấp chúng cho bạn. Đó là một trong những liên quan đến Charset.forName("UTF-8").newDecoder(). Tôi giải thích điều này một chút trong câu trả lời của tôi.
tchrist

1
Vâng, điều đó tốt hơn nhiều. Đó là nhiều thường xuyên hơn với lỗi nhập liệu mã hóa nơi này đi lên hơn nó đi kèm với đầu ra (ít nhất nếu đó là một hình thức UTF: 8-bit mã hóa đầu ra luôn mất-mất . Trong Unicode) Tuy nhiên, bạn có thể về mặt lý thuyết vẫn phải chịu họ về sản lượng vì Java cho phép những người đại diện lẻ để tồn tại trong chuỗi trong bộ nhớ (nó tới;! đây không phải là lỗi), nhưng không tuân thủ QTI UTF- {8,16,32} đầu ra bộ mã hóa được phép để sản xuất chúng trên đầu ra.
tchrist

9

Kể từ Java 11, bạn có thể làm:

FileWriter fw = new FileWriter("filename.txt", Charset.forName("utf-8"));

7

Kể từ Java 7, có một cách dễ dàng để xử lý mã hóa ký tự của BufferedWriter và BufferedReaders. Bạn có thể tạo BufferedWriter trực tiếp bằng cách sử dụng lớp Tệp thay vì tạo các phiên bản khác nhau của Writer. Bạn chỉ cần tạo một BufferedWriter, coi mã hóa ký tự, bằng cách gọi:

Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8);

Bạn có thể tìm thêm về nó trong JavaDoc:


5

Với văn bản tiếng Trung, tôi đã cố gắng sử dụng Charset UTF-16 và may mắn thay nó hoạt động.

Hy vọng điều này có thể giúp đỡ!

PrintWriter out = new PrintWriter( file, "UTF-16" );

có thể thử với UTF-32
anson 12/12/18

1

OK, bây giờ là năm 2019 và từ Java 11, bạn có một hàm tạo với Charset:

FileWriter​(String fileName, Charset charset)

Thật không may, chúng tôi vẫn không thể sửa đổi kích thước bộ đệm byte và nó được đặt thành 8192. ( https://www.baeldung.com/java-filewriter )


0

sử dụng OutputStream thay vì FileWriter để đặt kiểu mã hóa

// file is your File object where you want to write you data 
OutputStream outputStream = new FileOutputStream(file);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
outputStreamWriter.write(json); // json is your data 
outputStreamWriter.flush();
outputStreamWriter.close();

-3

theo ý kiến ​​của tôi

Nếu bạn muốn viết theo kiểu UTF-8, bạn nên tạo một mảng byte, sau đó bạn có thể làm như sau: byte[] by=("<?xml version=\"1.0\" encoding=\"utf-8\"?>"+"Your string".getBytes();

Sau đó, bạn có thể ghi từng byte vào tệp bạn đã tạo. Thí dụ:

OutputStream f=new FileOutputStream(xmlfile);
    byte[] by=("<?xml version=\"1.0\" encoding=\"utf-8\"?>"+"Your string".getBytes();
    for (int i=0;i<by.length;i++){
    byte b=by[i];
    f.write(b);

    }
    f.close();

Chào mừng bạn đến với Stack Overflow! Mặc dù đoạn mã này có thể giải quyết câu hỏi, nhưng bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho người đọc trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn. Cũng vui lòng cố gắng không chèn mã của bạn bằng các nhận xét giải thích, điều này làm giảm khả năng đọc của cả mã và giải thích!
Isiah Meadows
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.