Problem z kodowaniem Java FileReader

132

Próbowałem użyć java.io.FileReader do odczytania niektórych plików tekstowych i przekonwertowania ich na ciąg, ale okazało się, że wynik jest nieprawidłowo zakodowany i w ogóle nie jest czytelny.

Oto moje środowisko:

  • Windows 2003, kodowanie systemu operacyjnego: CP1252

  • Java 5.0

Moje pliki są zakodowane w UTF-8 lub CP1252, a niektóre z nich (pliki zakodowane w UTF-8) mogą zawierać znaki chińskie (inne niż łacińskie).

Do pracy używam następującego kodu:

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

Powyższy kod nie działa. Odkryłem, że kodowanie FileReadera to CP1252, nawet jeśli tekst jest zakodowany w UTF-8. Ale JavaDoc z java.io.FileReader mówi, że:

Konstruktorzy tej klasy zakładają, że domyślne kodowanie znaków i domyślny rozmiar buforu bajtów są odpowiednie.

Czy to oznacza, że ​​nie muszę samodzielnie ustawiać kodowania znaków, jeśli używam FileReader? Ale obecnie otrzymałem błędnie zakodowane dane. Jaki jest właściwy sposób radzenia sobie z moją sytuacją? Dzięki.

nybon
źródło
Powinieneś także zgubić String.valueOf () wewnątrz pętli i bezpośrednio użyć StringBuffer.append (char [], int, int). Oszczędza to wiele kopiowania znaku char []. Zastąp również StringBuffer StringBuilder. Jednak nic z tego nie dotyczy twojego pytania ”.
Joachim Sauer
1
Nienawidzę tego mówić, ale czy przeczytałeś JavaDoc zaraz po wklejeniu części? Wiesz, część, która mówi „Aby samodzielnie określić te wartości, utwórz InputStreamReader na FileInputStream.”?
Powerlord
Dziękuję za komentarz, właściwie czytałem JavaDoc, ale nie jestem pewien, czy powinienem sam określać te wartości i przełączyć się na „skonstruowanie InputStreamReader na FileInputStream”.
nybon
Tak, jeśli wiesz, że plik jest w czymś innym niż domyślne kodowanie platformy, musisz powiedzieć InputStreamReader, którego użyć.
Alan Moore

Odpowiedzi:

256

Tak, musisz określić kodowanie pliku, który chcesz przeczytać.

Tak, oznacza to, że musisz znać kodowanie pliku, który chcesz przeczytać.

Nie, nie ma ogólnego sposobu na odgadnięcie kodowania dowolnego pliku „zwykłego tekstu”.

Konstruktory jednargumentoweFileReader zawsze używają domyślnego kodowania platformy, co jest ogólnie złym pomysłem .

Od Java 11 FileReaderzyskały również konstruktory akceptujące kodowanie: new FileReader(file, charset)i new FileReader(fileName, charset).

We wcześniejszych wersjach java musisz użyć .new InputStreamReader(new FileInputStream(pathToFile), <encoding>)

Joachim Sauer
źródło
1
InputStream to = nowy FileInputStream (nazwa pliku); tutaj dostałem plik błędu nie znaleziono błędu z rosyjską nazwą pliku
Bhanu Sharma
3
+1 za sugestię użycia InputStreamReader, jednak użycie linków w blokach kodu utrudnia kopiowanie i wklejanie kodu, jeśli można to zmienić, thx
Ferrybig
1
Czy w kodowaniu będzie to „UTF-8” czy „UTF8”. Zgodnie z dokumentacją Java SE dotyczącą kodowania , skoro InputStreamReaderjest to java.ioklasa, to będzie to „UTF8”?
NobleUplift
9
@NobleUplift: najbezpieczniejszy jest zakład StandardCharsets.UTF_8, nie ma tam szans na pomyłkę ;-) Ale tak, jeśli pójdziesz ze stringiem, "UTF8"będzie to poprawne (chociaż wydaje mi się, że pamiętam, że zaakceptuje obie strony).
Joachim Sauer
1
@JoachimSauer Właściwie jest to jeden z celów Byte Order Mark, wraz z… cóż… ustaleniem kolejności bajtów! :) W związku z tym wydaje mi się dziwne, że FileReader Javy nie jest w stanie automatycznie wykryć UTF-16, który ma taki BOM ... Właściwie kiedyś napisałem taki, UnicodeFileReaderktóry robi dokładnie to. Niestety zamknięte źródło, ale Google ma swój UnicodeReader, który jest bardzo podobny.
Stijn de Witt,
79

FileReader używa domyślnego kodowania platformy Java, które zależy od ustawień systemowych komputera, na którym działa i jest ogólnie najpopularniejszym kodowaniem wśród użytkowników w tym regionie.

Jeśli to „najlepsze przypuszczenie” nie jest poprawne, musisz jawnie określić kodowanie. Niestety FileReadernie pozwala na to (duży niedopatrzenie w API). Zamiast tego musisz użyć new InputStreamReader(new FileInputStream(filePath), encoding)i najlepiej uzyskać kodowanie z metadanych dotyczących pliku.

Michael Borgwardt
źródło
24
„duże niedopatrzenie w API” - dzięki za to wyjaśnienie - zastanawiałem się, dlaczego nie mogę znaleźć konstruktora, którego szukam! Pozdrawiam John
monojohnny Kwietnia
@Bhanu Sharma: to problem z kodowaniem na innym poziomie, sprawdź, skąd pobierasz nazwę pliku i jeśli jest zakodowana na stałe, jakiego kodowania używa kompilator.
Michael Borgwardt,
1
@BhanuSharma: problemy z kodowaniem nazw plików nie mają nic wspólnego z tym pytaniem. Zobacz jedno z wielu istniejących pytań „dlaczego nazwy plików Unicode nie działają w Javie”. Spoiler: interfejsy API java.io, takie jak FileReader, używają wywołań systemu plików biblioteki standardowej C, które nie mogą obsługiwać Unicode w systemie Windows; zamiast tego rozważ użycie java.nio.
bobince
1
FileReaderwykorzystuje domyślne kodowanie platformy Java, które zależy od ustawień systemowych komputera, na którym jest uruchomiony, i jest generalnie najpopularniejszym kodowaniem wśród użytkowników w tym regionie”. Nie powiedziałbym tego. Przynajmniej Windows. Z pewnych dziwnych technicznych / historycznych powodów JVM ignoruje fakt, że Unicode jest zalecanym kodowaniem w systemie Windows dla „wszystkich nowych aplikacji” i zamiast tego zawsze zachowuje się tak, jakby starsze kodowanie skonfigurowane jako rezerwowe dla starszych aplikacji było „domyślną platformą”.
Stijn de Witt,
6
Powiedziałbym nawet, że jeśli twoja aplikacja Java nie określa wyraźnie kodowania za każdym razem, gdy odczytuje lub zapisuje do plików / strumieni / zasobów, jest zepsuta , ponieważ nie może wtedy działać niezawodnie.
Stijn de Witt,
8

Od wersji Java 11 możesz tego używać:

public FileReader(String fileName, Charset charset) throws IOException;
Radoslav Ivanov
źródło
7

W przypadku dokumentu Java 7+ możesz użyć tego:

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

Oto wszystkie dokumenty dotyczące Charsets

Na przykład, jeśli twój plik jest w CP1252, użyj tej metody

Charset.forName("windows-1252");

Oto inne nazwy kanoniczne dla Java kodowania zarówno do IO i NIO doc

Jeśli nie wiesz dokładnie z kodowania masz w pliku, można używać niektórych bibliotekami innych firm jak tego narzędzia od Google ten , który działa dość schludny.

Andreas Gelever
źródło
1

FileInputStream z InputStreamReader jest lepszy niż bezpośrednie używanie FileReader, ponieważ ten ostatni nie pozwala na określenie zestawu kodowania.

Oto przykład użycia razem BufferedReader, FileInputStream i InputStreamReader, abyś mógł czytać wiersze z pliku.

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

Dla innych języków łacińskich, na przykład cyrylicy, możesz użyć czegoś takiego:

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

i upewnij się, że .txtplik został zapisany w formacie UTF-8(ale nie jako domyślny ANSI). Twoje zdrowie!

Iefimenko Ievgwn
źródło