2009-01-31 19 views
6

Mam plik zakodowany jako iso-8859-1 i zawiera on znaki takie jak ô.Aplikacja Java: Nie można poprawnie odczytać pliku kodowanego izo-8859-1

Czytam ten plik z kodu java, coś jak:

File in = new File("myfile.csv"); 
InputStream fr = new FileInputStream(in); 
byte[] buffer = new byte[4096]; 
while (true) { 
    int byteCount = fr.read(buffer, 0, buffer.length); 
    if (byteCount <= 0) { 
     break; 
    } 

    String s = new String(buffer, 0, byteCount,"ISO-8859-1"); 
    System.out.println(s); 
} 

Jednak postać O jest zawsze zniekształcone, zazwyczaj drukuje jak? .

Przeczytałem na ten temat (i dowiedziałem się trochę po drodze) np.

ale nadal nie mogę dostać tej pracy

Co ciekawe to działa na moim komputerze lokalnym (XP), ale nie na moim Linuksie.

Sprawdziłem, że mój JDK obsługuje wymaganych zestawów znaków (są standardem, więc nie jest to niespodzianka), używając:

System.out.println(java.nio.charset.Charset.availableCharsets()); 
+0

Powinienem dodać, że jestem w stanie poprawnie odczytać znaki lub oryginalny plik za pomocą mojego terminalu linuxowego, jeśli po prostu przetnę zawartość pliku – Joel

+0

Jakie kodowanie znaków jest używane przez twój terminal? – McDowell

+0

Co ciekawe - jeśli dodaję właściwość java środowiska wykonawczego "-Dfile.encoding = UTF16", działa ona zgodnie z oczekiwaniami, chociaż nie rozumiem, dlaczego ma to znaczenie - i nie uważam tego za rozwiązanie, ale raczej za włamanie. Nie działa z właściwością ustawioną na UTF8. – Joel

Odpowiedz

12

Podejrzewam, że Twój plik nie jest faktycznie zakodowany jako ISO-8859-1, lub System.out nie wie, jak wydrukować znak.

Zalecamy sprawdzenie pierwszego, aby sprawdzić odpowiedni bajt w pliku. Aby sprawdzić na sekundę, zbadać odpowiedni znak w łańcuchu, drukując go z

System.out.println((int) s.getCharAt(index)); 

W obu przypadkach wynik powinno być 244 dziesiętny; 0xf4 hex.

Aby uzyskać ogólne porady, zobacz my article on Unicode debugging (kod przedstawiony jest w języku C#, ale można go łatwo przekonwertować na język Java, a zasady są takie same).

Ogólnie rzecz biorąc, chciałbym zawinąć strumień z InputStreamReader z odpowiednim kodowaniem - jest to łatwiejsze niż tworzenie nowych ciągów "ręcznie". Rozumiem, że może to być po prostu kod demo.

EDIT: Tutaj jest naprawdę łatwy sposób udowodnić, czy konsola będzie działać:

System.out.println("Here's the character: \u00f4"); 
+0

użyłem narzędzia pliku linux do przetestowania typu pliku: plik --mime FranceJ2.csv FranceJ2.csv: text/plain; charset = iso-8859-1 , a także potwierdziłem, że mogę go poprawnie odczytać, powiedzmy, , ale postąpię zgodnie z Twoimi sugestiami. – Joel

+1

Nie ufaj narzędziom, które próbują automatycznie wykryć kodowanie znaków. Zawsze opierają się tylko na heurystyce i muszą być. Nie wiedzą, jaki tekst powinien zawierać twój plik. –

+0

Sześciokopia pliku daje: 0000000 0df4 000a (wszelkie sugestie !?) – Joel

3

Jeśli możesz, spróbuj uruchomić program w debugger, aby zobaczyć, co jest wewnątrz ciąg 's' po utworzeniu. Możliwe, że ma poprawną zawartość, ale wynik wyjściowy jest zniekształcony po wywołaniu System.out.println (s). W takim przypadku prawdopodobnie istnieje niezgodność między tym, co Java myśli, koduje twoje wyjście i kodowanie znaków twojego terminala/konsoli w systemie Linux.

9

parsowania pliku jako bloki o stałej wielkości bajtów nie jest dobry --- co jeśli jakiś ma charakter reprezentacja bajtów, która rozciąga się na dwa bloki? Użyj InputStreamReader z odpowiednim kodowaniem znaków zamiast:

BufferedReader br = new BufferedReader(
     new InputStreamReader(
     new FileInputStream("myfile.csv"), "ISO-8859-1"); 

char[] buffer = new char[4096]; // character (not byte) buffer 

while (true) 
{ 
     int charCount = br.read(buffer, 0, buffer.length); 

     if (charCount == -1) break; // reached end-of-stream 

     String s = String.valueOf(buffer, 0, charCount); 
     // alternatively, we can append to a StringBuilder 

     System.out.println(s); 
} 

Btw, pamiętaj, aby sprawdzić, czy znak Unicode można faktycznie prawidłowo wyświetlane.Możesz także przekierować wyjście programu do pliku, a następnie porównać go z oryginalnym plikiem.

Zgodnie z sugestią Jon Skeet problem może być związany z konsolą. Wypróbuj System.console().printf(s), aby sprawdzić, czy istnieje różnica.

1

Zasadniczo, jeśli działa on na lokalnym komputerze PC XP, ale nie na Linuksie, i parsujesz dokładnie ten sam plik (tj. Przeniesiesz go w sposób binarny między polami), to prawdopodobnie ma to coś wspólnego z Wywołanie System.out.println. Nie wiem, jak sprawdzić dane wyjściowe, ale jeśli robisz to, łącząc się ze zdalną powłoką z pudełka XP, to jest zestaw znaków powłoki (i klienta) do rozważenia.

Co więcej, sugeruje to również Zach Scrivena - nie można zakładać, że można w ten sposób utworzyć łańcuchy z fragmentów danych - najpierw użyć InputStreamReader lub najpierw odczytać kompletne dane do tablicy (oczywiście nie będzie działać dla duży plik). Jednak, ponieważ wydaje się, że działa na XP, to zaryzykowałbym, że to prawdopodobnie nie jest twój problem w tym konkretnym przypadku.

6

@Joel - your own answer potwierdza, że ​​problem stanowi różnicę między domyślnym kodowaniem w systemie operacyjnym (UTF-8, którego Java podniosła) i kodowaniem terminala (ISO-8859-1).

Rozważmy następujący kod:

public static void main(String[] args) throws IOException { 
    byte[] data = { (byte) 0xF4 }; 
    String decoded = new String(data, "ISO-8859-1"); 
    if (!"\u00f4".equals(decoded)) { 
     throw new IllegalStateException(); 
    } 

    // write default charset 
    System.out.println(Charset.defaultCharset()); 

    // dump bytes to stdout 
    System.out.write(data); 

    // will encode to default charset when converting to bytes 
    System.out.println(decoded); 
} 

Domyślnie moim Ubuntu (8.04) terminal używa kodowania UTF-8. Z tego kodowania, to jest wydrukowane:

UTF-8
& # x00F4;

Jeśli przełącznik kodowania terminala zgodnie z ISO 8859-1, jest drukowany:

UTF-8
& # x00F4; & # x00C3; & # x00B4;

W obu przypadkach te same bajty są emitowane przez program Java:

5554 462d 380a f4c3 b40a 

Jedyną różnicą jest to, w jaki sposób terminal interpretacji bajtów, które otrzymuje. W ISO 8859-1, & # x00F4; jest zakodowany jako 0xF4. W UTF-8, & # x00F4; jest zakodowany jako 0xC3B4. Pozostałe znaki są wspólne dla obu kodowań.

+0

Z pewnością coś tu brakuje - co to jest '5554 462d 380a f4c3 b40a' dump? Na pewno nie wywołanie 'System.out.write (data)'? –

+1

@Mr_and_Mrs_D Są to bajty, które JRE zapisuje na urządzeniu (STDOUT) z wszystkimi trzema wywołaniami do "System.out". Bajki '0A' zaznaczają znaki nowej linii napisane przez' println'. _Nie było odpowiedzi napisanej przez autora pytania, ponieważ skasowany, ale nie sądzę, aby móc go przeczytać dodaje dużo._ – McDowell

+0

Dzięki za śledzenie - zrozumiałem, że była to odpowiedź autora po skasowaniu - nie mogę go przeczytać - dzięki :) –