Zapisać plik w UTF-8 za pomocą FileWriter (Java)?

82

Mam jednak następujący kod, ale chcę, aby był zapisywany jako plik UTF-8 do obsługi znaków obcych. Czy jest na to sposób, czy istnieje potrzeba posiadania parametru?

Byłbym naprawdę wdzięczny za twoją pomoc w tym. Dzięki.

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();
}
user1280970
źródło

Odpowiedzi:

77

Konstruktory bezpiecznego kodowania

Zmuszenie Javy do prawidłowego powiadamiania o błędach kodowania jest trudne. Musisz użyć najbardziej rozwlekłego i, niestety, najmniej używanego z czterech alternatywnych konstruktorów dla każdego z nich InputStreamReaderi OutputStreamWriterotrzymać odpowiedni wyjątek od usterki kodowania.

W przypadku wejścia / wyjścia pliku zawsze upewnij się, że zawsze używasz drugiego argumentu obu OutputStreamWriteri InputStreamReaderfantazyjnego argumentu kodera:

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

Istnieją inne, nawet bardziej wyszukane możliwości, ale żadna z trzech prostszych możliwości nie działa w przypadku obsługi wyjątków. Te robią:

 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() 
 );

Jeśli chodzi o bieganie z

 $ java -Dfile.encoding=utf8 SomeTrulyRemarkablyLongcLassNameGoeShere

Problem polega na tym, że nie użyje on pełnej formy argumentu kodera dla strumieni znaków, więc ponownie przegapisz problemy z kodowaniem.

Dłuższy przykład

Oto dłuższy przykład, ten zarządzający procesem zamiast plikiem, w którym promujemy dwa różne strumienie bajtów wejściowych i jeden strumień bajtów wyjściowych do strumieni znaków UTF-8 z pełną obsługą wyjątków :

 // 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()
                         );

Teraz masz trzy strumienie znakowe że wszystko Wyjątek podbicie na błędy kodowania, odpowiednio nazwane chars_into_his_stdin, chars_from_his_stdouti chars_from_his_stderr.

Jest to tylko trochę bardziej skomplikowane niż to, czego potrzebujesz do swojego problemu, którego rozwiązanie podałem w pierwszej połowie tej odpowiedzi. Najważniejsze jest to, że jest to jedyny sposób wykrywania błędów kodowania.

Tylko nie zaczynaj mi o PrintStreamwyjątkach żywieniowych.

tchrist
źródło
1
Świetna odpowiedź, ale myślę, że jest w niej drobny błąd - InputStreamReader char_input = new InputStreamWriterpowinien przeczytać:, InputStreamReader char_input = new InputStreamReader a InputStreamReaderkonstruktor bierze a CharsetDecoder, a nie CharsetEncoder.
Mark Rhodes,
Ale czy jest to prawdziwy problem, którego UTF-8 nie jest w stanie przedstawić, pomyślałem, że może zakodować wszystko.
Paul Taylor
Jeśli chcesz narzekać, że strumienie jedzą wyjątki, spróbuj CipherInputStream, to usuwa je BadPaddingException, nawet jeśli są tworzone przez uwierzytelniony strumień szyfrowania :(
Maarten Bodewes
Znalazłem mały błąd w Twoim kodzie: „Charset.forName („ UTF-8 ”). NewEncoder ()” dla „InputStreamReader” powinno mieć wartość „Charset.forName („ UTF-8 ”). NewDecoder ()”. Czyli „dekoder” zamiast „kodera”. W każdym razie dziękuję za tę miłą odpowiedź i +1. :)
ścieżka kodowa
2
(Cały system Java IO zawsze był bałaganem. Powinien zostać całkowicie przerobiony, jak przerobione daty Joda Time.)
Tuntable
56

Ditch FileWriteri FileReader, które są bezużyteczne właśnie dlatego, że nie pozwalają na określenie kodowania. Zamiast tego użyj

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

i

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

Michael Borgwardt
źródło
12
Jeśli nie użyjesz bardzo rozwlekłego Charset.forName("UTF-8").newDecoder()argumentu (lub jakiejś bardziej wyszukanej konstrukcji) zamiast po prostu "UTF-8", nie zostaniesz odpowiednio powiadomiony o błędach kodowania (czytaj: wyjątki zostaną zniesione i tajemniczo ukryje błędy kodowania).
tchrist
3
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8 )
Abdull,
46

Musisz użyć OutputStreamWriterklasy jako parametru zapisującego dla swojego BufferedWriter. Akceptuje kodowanie. Przejrzyj javadocs .

Trochę tak:

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

Lub możesz ustawić bieżące kodowanie systemu za pomocą właściwości systemowej file.encodingna UTF-8.

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

Możesz również ustawić go jako właściwość systemową w czasie wykonywania, System.setProperty(...)jeśli potrzebujesz go tylko dla tego konkretnego pliku, ale w takim przypadku myślę, że wolałbymOutputStreamWriter .

Ustawiając właściwość systemową, możesz używać FileWriteri oczekiwać, że będzie on używał UTF-8 jako domyślnego kodowania dla twoich plików. W tym przypadku dla wszystkich plików, które czytasz i zapisujesz.

EDYTOWAĆ

  • Począwszy od API 19, możesz zamienić ciąg znaków „UTF-8” na StandardCharsets.UTF_8

  • Jak zasugerował w komentarzach poniżej tchrist , jeśli zamierzasz wykryć błędy kodowania w swoim pliku, będziesz zmuszony użyć tego OutputStreamWriterpodejścia i użyć konstruktora, który otrzymuje koder zestawu znaków.

    Coś jak

    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));
    

    Możesz wybierać między działaniami IGNORE | REPLACE | REPORT

Również na to pytanie udzielono już tutaj odpowiedzi .

Edwin Dalorzo
źródło
To nie wystarczy. Potrzebujesz również InputStreamReader(InputStream in, CharsetDecoder dec)takiego, że ostatnim argumentem jest Charset.forName("UTF-8").newDecoder().
tchrist
1
Jeśli to zrobisz, błędy w kodowaniu danych wejściowych zostaną po cichu usunięte.
tchrist
Nie ma potrzeby stosowania kodera. Konstruktor akceptuje String, Charset lub Encoder w obu klasach Input / Output. Nie wiem, co masz na myśli, mówiąc o swoim komentarzu. Czy możesz to rozwinąć, proszę?
Edwin Dalorzo
3
@edalorzo Jeśli przetestujesz cztery różne {In,Out}putStream{Reader,Writer}konstruktory na błędnych danych, odkryjesz, że trzy z nich maskują wszystkie wyjątki, które powinny wynikać z błędów kodowania, a tylko czwarta forma poprawnie je dostarcza. To jest ten, który obejmuje Charset.forName("UTF-8").newDecoder(). Wyjaśniam to trochę w mojej odpowiedzi.
tchrist
1
Tak, to znacznie lepiej. To znacznie częściej z wejściowych kodowania błędów gdzie to pochodzi się niż to wyjdzie z wyjściem (przynajmniej jeśli jest to forma UTF: 8-bitowe kodowanie wyjścia są zawsze przegrana . Unicode) Można jednak teoretycznie nadal je ponieść na wyjściu, ponieważ Java pozwala na istnienie niesparowanych surogatów w łańcuchach w pamięci ( musi ; to nie jest błąd!), ale żaden zgodny koder wyjściowy UTF- {8,16,32} nie może wytworzyć ich na wyjściu.
tchrist
9

Od wersji Java 11 możesz:

FileWriter fw = new FileWriter("filename.txt", Charset.forName("utf-8"));
mortensi
źródło
7

Od Java 7 istnieje łatwy sposób obsługi kodowania znaków BufferedWriter i BufferedReaders. Możesz utworzyć BufferedWriter bezpośrednio przy użyciu klasy Files zamiast tworzenia różnych wystąpień Writer. Możesz po prostu utworzyć BufferedWriter, który uwzględnia kodowanie znaków, wywołując:

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

Więcej na ten temat można znaleźć w JavaDoc:

Lars Briem
źródło
5

W przypadku tekstu chińskiego próbowałem użyć zestawu znaków UTF-16 i na szczęście działa.

Mam nadzieję, że to może pomóc!

PrintWriter out = new PrintWriter( file, "UTF-16" );
Phuong
źródło
można spróbować z UTF-32
anson
1

OK, teraz jest 2019, a od Java 11 masz konstruktora z Charset:

FileWriter​(String fileName, Charset charset)

Niestety nadal nie możemy zmodyfikować rozmiaru bufora bajtów i jest on ustawiony na 8192. ( https://www.baeldung.com/java-filewriter )

kod đờ
źródło
0

użyj OutputStream zamiast FileWriter, aby ustawić typ kodowania

// 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();
zakaria
źródło
-3

W mojej opinii

Jeśli chcesz pisać według rodzaju UTF-8, powinieneś utworzyć tablicę bajtów, a następnie wykonać następujące czynności: byte[] by=("<?xml version=\"1.0\" encoding=\"utf-8\"?>"+"Your string".getBytes();

Następnie możesz zapisać każdy bajt do utworzonego pliku. Przykład:

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();
Phan Ngọc Hoàng Dương
źródło
Witamy w Stack Overflow! Chociaż ten fragment kodu może rozwiązać problem, dołączenie wyjaśnienia naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a osoby te mogą nie znać powodów, dla których zaproponowałeś kod. Prosimy również starać się nie tłoczyć kodu komentarzami wyjaśniającymi, co zmniejsza czytelność zarówno kodu, jak i wyjaśnień!
Isiah Meadows