Kompletny zestaw znaków, aby uniknąć „java.nio.charset.MalformedInputException: Input length = 1”?

97

Tworzę prosty program do liczenia słów w Javie, który czyta pliki tekstowe katalogu.

Jednak ciągle otrzymuję błąd:

java.nio.charset.MalformedInputException: Input length = 1

z tego wiersza kodu:

BufferedReader reader = Files.newBufferedReader(file,Charset.forName("UTF-8"));

Wiem, że prawdopodobnie to rozumiem, ponieważ użyłem Charset znaku, który nie zawierał niektórych znaków w plikach tekstowych, z których niektóre zawierały znaki innych języków. Ale chcę uwzględnić te postacie.

Później dowiedziałem się w JavaDocs, że Charsetjest opcjonalny i używany tylko do bardziej wydajnego odczytu plików, więc zmieniłem kod na:

BufferedReader reader = Files.newBufferedReader(file);

Ale niektóre pliki nadal wyrzucają MalformedInputException. Nie wiem dlaczego.

Zastanawiałem się, czy istnieje all-inclusive Charset, który pozwoli mi czytać pliki tekstowe z wieloma różnymi typami znaków ?

Dzięki.

Jonathan Lam
źródło

Odpowiedzi:

82

Prawdopodobnie chcesz mieć listę obsługiwanych kodowań. Dla każdego pliku wypróbuj każde kodowanie po kolei, być może zaczynając od UTF-8. Za każdym razem, gdy złapiesz MalformedInputException, spróbuj następnego kodowania.

Dawood ibn Kareem
źródło
45
Próbowałem ISO-8859-1i działa dobrze. Myślę, że to dla europejskich postaci, co jest w porządku. Nadal nie wiem, dlaczego UTF-16nie działa.
Jonathan Lam,
1
Jeśli masz Notepad ++, możesz spróbować otworzyć plik tekstowy, który powie Ci kodowanie pliku w Menu. Możesz wtedy dostosować kod w sposób dowolny, jeśli zawsze otrzymujesz plik z tego samego źródła.
JGFMK
@JonathanLam Cóż, ponieważ jeśli jest zakodowany ISO-8859-1, to nie jest UTF-16 . Te kodowania są zupełnie inne. Plik nie może być jednym i drugim.
Dawood ibn Kareem
@DawoodsaysreinstateMonica Myślę, że chodziło mi o to, że byłem zaskoczony, iż UTF-16 nie działał tak dobrze, jak wydaje się, że działa tak dobrze dla znaków europejskich, takich jak ISO-8859-1. Ale dzięki za informacje (nawet jeśli sześć lat później): P
Jonathan Lam
Pewnie. UTF-16 zawiera wszystkie europejskie znaki. Ale są reprezentowane inaczej niż ISO-8859-1. W ISO-8859-1 wszystkie znaki są reprezentowane za pomocą tylko 8 bitów, więc masz ograniczenie do 256 możliwych znaków. W UTF-16 większość znaków jest reprezentowana za pomocą 16 bitów, a niektóre znaki są reprezentowane za pomocą 32 bitów. Tak więc w UTF-16 jest o wiele więcej możliwych znaków, ale plik ISO-8859-1 będzie wymagał tylko o połowę mniej miejsca niż te same dane, które wykorzystywałyby w UTF-16.
Dawood ibn Kareem
41

Tworzenie BufferedReader z Files.newBufferedReader

Files.newBufferedReader(Paths.get("a.txt"), StandardCharsets.UTF_8);

podczas uruchamiania aplikacji może zgłosić następujący wyjątek:

java.nio.charset.MalformedInputException: Input length = 1

Ale

new BufferedReader(new InputStreamReader(new FileInputStream("a.txt"),"utf-8"));

działa dobrze.

Różnica polega na tym, że ta pierwsza używa domyślnej akcji CharsetDecoder.

Domyślnym działaniem w przypadku błędnych danych wejściowych i błędów związanych ze znakami niemapowalnymi jest ich zgłoszenie .

podczas gdy ta ostatnia używa akcji REPLACE.

cs.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE)
Xin Wang
źródło
29

ISO-8859-1 to kompletny zestaw znaków, w tym sensie, że gwarantuje, że nie zgłosi wyjątku MalformedInputException. Jest więc dobry do debugowania, nawet jeśli twoje dane wejściowe nie są w tym zestawie znaków. Więc:-

req.setCharacterEncoding("ISO-8859-1");

Miałem kilka znaków podwójnego cudzysłowu z prawej strony / podwójnego cudzysłowu z lewej strony w danych wejściowych i zarówno US-ASCII, jak i UTF-8 rzuciły na nie MalformedInputException, ale ISO-8859-1 działało.

Tim Cooper
źródło
7

Napotkałem również ten wyjątek z komunikatem o błędzie,

java.nio.charset.MalformedInputException: Input length = 1
at java.nio.charset.CoderResult.throwException(Unknown Source)
at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
at sun.nio.cs.StreamEncoder.write(Unknown Source)
at java.io.OutputStreamWriter.write(Unknown Source)
at java.io.BufferedWriter.flushBuffer(Unknown Source)
at java.io.BufferedWriter.write(Unknown Source)
at java.io.Writer.write(Unknown Source)

i odkryłem, że podczas próby użycia pojawia się jakiś dziwny błąd

BufferedWriter writer = Files.newBufferedWriter(Paths.get(filePath));

napisać rzutowanie typu String "orazg 54" z typu ogólnego w klasie.

//key is of generic type <Key extends Comparable<Key>>
writer.write(item.getKey() + "\t" + item.getValue() + "\n");

Ten łańcuch ma długość 9 i zawiera znaki z następującymi punktami kodowymi:

111 114 97 122 103 9 53 52 10

Jeśli jednak BufferedWriter w klasie zostanie zastąpiony przez:

FileOutputStream outputStream = new FileOutputStream(filePath);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));

może z powodzeniem napisać ten ciąg bez wyjątków. Ponadto, jeśli napiszę ten sam ciąg utworzony ze znaków, nadal działa OK.

String string = new String(new char[] {111, 114, 97, 122, 103, 9, 53, 52, 10});
BufferedWriter writer = Files.newBufferedWriter(Paths.get("a.txt"));
writer.write(string);
writer.close();

Wcześniej nigdy nie napotkałem żadnego wyjątku podczas używania pierwszego BufferedWriter do pisania jakichkolwiek ciągów. To dziwny błąd, który występuje w BufferedWriter utworzonym z java.nio.file.Files.newBufferedWriter (ścieżka, opcje)

Tomek
źródło
1
To trochę nie na temat, ponieważ PO mówił o czytaniu, a nie pisaniu. Miałem podobny problem z powodu BufferedWriter.write (int) - który traktuje ten int jako znak i zapisuje go bezpośrednio do strumienia. Sposób obejścia problemu polega na ręcznym przekonwertowaniu go na ciąg, a następnie zapisaniu.
malaverdiere
To jest niestety niedotrzymana odpowiedź. Naprawdę fajna robota Tom. Zastanawiam się, czy problem ten został rozwiązany w późniejszych wersjach Javy.
Ryboflavin
5

ISO_8859_1 Pracowało dla mnie! Czytałem plik tekstowy z wartościami oddzielonymi przecinkami

Shahid Hussain Abbasi
źródło
4

spróbuj tego .. miałem ten sam problem, poniżej implementacja działała dla mnie

Reader reader = Files.newBufferedReader(Paths.get(<yourfilewithpath>), StandardCharsets.ISO_8859_1);

następnie używaj programu Reader, gdziekolwiek chcesz.

foreg:

CsvToBean<anyPojo> csvToBean = null;
    try {
        Reader reader = Files.newBufferedReader(Paths.get(csvFilePath), 
                        StandardCharsets.ISO_8859_1);
        csvToBean = new CsvToBeanBuilder(reader)
                .withType(anyPojo.class)
                .withIgnoreLeadingWhiteSpace(true)
                .withSkipLines(1)
                .build();

    } catch (IOException e) {
        e.printStackTrace();
    }
Vin
źródło
3

Napisałem poniżej, aby wydrukować listę wyników do standaryzacji w oparciu o dostępne zestawy znaków. Zwróć uwagę, że informuje również o tym, która linia nie działa z numeru linii opartego na 0, w przypadku rozwiązywania problemów ze znakiem powodującym problemy.

public static void testCharset(String fileName) {
    SortedMap<String, Charset> charsets = Charset.availableCharsets();
    for (String k : charsets.keySet()) {
        int line = 0;
        boolean success = true;
        try (BufferedReader b = Files.newBufferedReader(Paths.get(fileName),charsets.get(k))) {
            while (b.ready()) {
                b.readLine();
                line++;
            }
        } catch (IOException e) {
            success = false;
            System.out.println(k+" failed on line "+line);
        }
        if (success) 
            System.out.println("*************************  Successs "+k);
    }
}
EngineerWithJava54321
źródło
0

Cóż, problem polega na tym, że Files.newBufferedReader(Path path)jest to realizowane w następujący sposób:

public static BufferedReader newBufferedReader(Path path) throws IOException {
    return newBufferedReader(path, StandardCharsets.UTF_8);
}

więc w zasadzie nie ma sensu określać, UTF-8chyba że chcesz, aby kod był opisowy. Jeśli chcesz wypróbować „szerszy” zestaw znaków, możesz spróbować StandardCharsets.UTF_16, ale i tak nie możesz być w 100% pewien, że otrzymasz wszystkie możliwe znaki.

francesco foresti
źródło
-1

możesz spróbować czegoś takiego lub po prostu skopiować i wkleić poniższy fragment.

boolean exception = true;
Charset charset = Charset.defaultCharset(); //Try the default one first.        
int index = 0;

while(exception) {
    try {
        lines = Files.readAllLines(f.toPath(),charset);
          for (String line: lines) {
              line= line.trim();
              if(line.contains(keyword))
                  values.add(line);
              }           
        //No exception, just returns
        exception = false; 
    } catch (IOException e) {
        exception = true;
        //Try the next charset
        if(index<Charset.availableCharsets().values().size())
            charset = (Charset) Charset.availableCharsets().values().toArray()[index];
        index ++;
    }
}
Pengxiang
źródło
Program obsługi wyjątków może potencjalnie uczynić while(exception)pętlę wieczną, jeśli nigdy nie znajdzie działającego zestawu znaków w tablicy. Program obsługi wyjątków powinien ponownie zgłosić, jeśli osiągnięty zostanie koniec tablicy i nie zostanie znaleziony działający zestaw znaków. Ponadto w chwili pisania tej odpowiedzi liczba głosów wynosiła „-2”. Głosowałem za „-1”. Myślę, że powodem, dla którego otrzymała głosy negatywne, jest niewystarczające wyjaśnienie. Chociaż rozumiem, co robi kod, inni mogą nie. Dlatego komentarz typu „możesz spróbować czegoś takiego” może nie zostać doceniony przez niektórych ludzi.
mvanle
-1

UTF-8 działa dla mnie z polskimi znakami

Adriano
źródło