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

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.


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


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 .


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

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:


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

można spróbować z UTF-32
— anson


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

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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.