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.

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.


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)

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.


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)


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


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

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

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.


-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 ++;
    }
}

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

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.