Java: как определить правильную кодировку кодировки потока

со ссылкой на следующий поток: Java-приложение: невозможно правильно прочитать кодированный файл iso-8859-1

каков наилучший способ программно определить правильную кодировку кодировки inputstream/файла ?

Я попытался использовать следующее:

File in =  new File(args[0]);
InputStreamReader r = new InputStreamReader(new FileInputStream(in));
System.out.println(r.getEncoding());

но в файле, который, как я знаю, кодируется ISO8859_1, приведенный выше код дает ASCII, что неверно и не позволяет мне правильно отображать содержимое файл в консоль.

15 ответов


Я использовал эту библиотеку, похожую на jchardet для обнаружения кодировки в Java: http://code.google.com/p/juniversalchardet/


вы не можете определить кодировку произвольного потока байтов. Такова природа кодировок. Кодировка означает сопоставление между значением байта и его представлением. Таким образом, каждая кодировка "может" быть правильной.

на getEncoding () метод вернет кодировку, которая была настроена (прочитайте JavaDoc) для потока. Он не угадает кодировку для вас.

некоторые потоки сообщают вам, какая кодировка использовалась для их создания: XML, HTML. Но не произвольный поток байтов.

в любом случае, вы можете попытаться угадать кодировку самостоятельно, если вам нужно. Каждый язык имеет общую частоту для каждого символа. В английском языке char e появляется очень часто, но ê будет появляться очень редко. В потоке ISO-8859-1 обычно нет символов 0x00. Но в потоке UTF-16 Их много.

или: вы можете спросить пользователя. Я уже видел приложения, которые представляют вам фрагмент файла в разных кодировках и спрашивают вас чтобы выбрать "правильный".


зацени: http://site.icu-project.org/ (icu4j) у них есть библиотеки для обнаружения кодировки из IOStream может быть просто так:

BufferedInputStream bis = new BufferedInputStream(input);
CharsetDetector cd = new CharsetDetector();
cd.setText(bis);
CharsetMatch cm = cd.detect();

if (cm != null) {
   reader = cm.getReader();
   charset = cm.getName();
}else {
   throw new UnsupportedCharsetException()
}

вот мои любимые:

TikaEncodingDetector

зависимость:

<dependency>
  <groupId>org.apache.any23</groupId>
  <artifactId>apache-any23-encoding</artifactId>
  <version>1.1</version>
</dependency>

пример:

public static Charset guessCharset(InputStream is) throws IOException {
  return Charset.forName(new TikaEncodingDetector().guessEncoding(is));    
}

GuessEncoding

зависимость:

<dependency>
  <groupId>org.codehaus.guessencoding</groupId>
  <artifactId>guessencoding</artifactId>
  <version>1.4</version>
  <type>jar</type>
</dependency>

пример:

  public static Charset guessCharset2(File file) throws IOException {
    return CharsetToolkit.guessEncoding(file, 4096, StandardCharsets.UTF_8);
  }

можно, конечно,проверка файл для определенной кодировки по декодирования С CharsetDecoder и следить за" неправильным вводом "или" неприменимыми символами " ошибок. Конечно, это только говорит вам, если кодировка неверна; он не говорит вам, если это правильно. Для этого вам нужна основа сравнения для оценки декодированных результатов, например, знаете ли вы заранее, ограничены ли символы некоторым подмножеством или текст придерживается какой строгий формат? Суть в том, что обнаружение кодировок-это догадки без каких-либо гарантий.


библиотеки выше-это простые детекторы BOM, которые, конечно, работают только в том случае, если в начале файла есть BOM. Взгляните на http://jchardet.sourceforge.net/ который сканирует текст


Я нашел хорошую стороннюю библиотеку, которая может обнаруживать фактическую кодировку: http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding

Я не тестировал его широко, но, похоже, он работает.


Если вы используете ICU4J (http://icu-project.org/apiref/icu4j/)

вот мой код:

            String charset = "ISO-8859-1"; //Default chartset, put whatever you want

            byte[] fileContent = null;
            FileInputStream fin = null;

            //create FileInputStream object
            fin = new FileInputStream(file.getPath());

            /*
             * Create byte array large enough to hold the content of the file.
             * Use File.length to determine size of the file in bytes.
             */
            fileContent = new byte[(int) file.length()];

            /*
             * To read content of the file in byte array, use
             * int read(byte[] byteArray) method of java FileInputStream class.
             *
             */
            fin.read(fileContent);

            byte[] data =  fileContent;

            CharsetDetector detector = new CharsetDetector();
            detector.setText(data);

            CharsetMatch cm = detector.detect();

            if (cm != null) {
                int confidence = cm.getConfidence();
                System.out.println("Encoding: " + cm.getName() + " - Confidence: " + confidence + "%");
                //Here you have the encode name and the confidence
                //In my case if the confidence is > 50 I return the encode, else I return the default value
                if (confidence > 50) {
                    charset = cm.getName();
                }
            }

Не забудьте поставить все попытки поймать нужно это.

Я надеюсь, что это работает для вас.


какую библиотеку использовать?

на момент написания этой книги они представляют собой три библиотеки, которые появляются:

Я не включаю Apache Any23 потому что он использует ICU4j 3.4 под капотом.

Как сказать, какой из них обнаружил право charset (или так близко, как возможно)?

невозможно подтвердить кодировку, обнаруженную каждой из вышеперечисленных библиотек. Тем не менее, можно спросить их по очереди и набрать возвращенный ответ.

Как забить возвращенный ответ?

каждому ответу может быть присвоена одна точка. Чем больше точек имеет ответ, тем больше уверенности имеет обнаруженная кодировка. Это простой метод подсчета очков. Вы можете разработать другие.

есть ли пример кода?

здесь полный фрагмент, реализующий стратегию, описанную в предыдущих строках.

public static String guessEncoding(InputStream input) throws IOException {
    // Load input data
    long count = 0;
    int n = 0, EOF = -1;
    byte[] buffer = new byte[4096];
    ByteArrayOutputStream output = new ByteArrayOutputStream();

    while ((EOF != (n = input.read(buffer))) && (count <= Integer.MAX_VALUE)) {
        output.write(buffer, 0, n);
        count += n;
    }

    if (count > Integer.MAX_VALUE) {
        throw new RuntimeException("Inputstream too large.");
    }

    byte[] data = output.toByteArray();

    // Detect encoding
    Map<String, int[]> encodingsScores = new HashMap<>();

    // * GuessEncoding
    updateEncodingsScores(encodingsScores, new CharsetToolkit(data).guessEncoding().displayName());

    // * ICU4j
    CharsetDetector charsetDetector = new CharsetDetector();
    charsetDetector.setText(data);
    charsetDetector.enableInputFilter(true);
    CharsetMatch cm = charsetDetector.detect();
    if (cm != null) {
        updateEncodingsScores(encodingsScores, cm.getName());
    }

    // * juniversalchardset
    UniversalDetector universalDetector = new UniversalDetector(null);
    universalDetector.handleData(data, 0, data.length);
    universalDetector.dataEnd();
    String encodingName = universalDetector.getDetectedCharset();
    if (encodingName != null) {
        updateEncodingsScores(encodingsScores, encodingName);
    }

    // Find winning encoding
    Map.Entry<String, int[]> maxEntry = null;
    for (Map.Entry<String, int[]> e : encodingsScores.entrySet()) {
        if (maxEntry == null || (e.getValue()[0] > maxEntry.getValue()[0])) {
            maxEntry = e;
        }
    }

    String winningEncoding = maxEntry.getKey();
    //dumpEncodingsScores(encodingsScores);
    return winningEncoding;
}

private static void updateEncodingsScores(Map<String, int[]> encodingsScores, String encoding) {
    String encodingName = encoding.toLowerCase();
    int[] encodingScore = encodingsScores.get(encodingName);

    if (encodingScore == null) {
        encodingsScores.put(encodingName, new int[] { 1 });
    } else {
        encodingScore[0]++;
    }
}    

private static void dumpEncodingsScores(Map<String, int[]> encodingsScores) {
    System.out.println(toString(encodingsScores));
}

private static String toString(Map<String, int[]> encodingsScores) {
    String GLUE = ", ";
    StringBuilder sb = new StringBuilder();

    for (Map.Entry<String, int[]> e : encodingsScores.entrySet()) {
        sb.append(e.getKey() + ":" + e.getValue()[0] + GLUE);
    }
    int len = sb.length();
    sb.delete(len - GLUE.length(), len);

    return "{ " + sb.toString() + " }";
}

улучшения: The guessEncoding метод полностью считывает inputstream. Для больших inputstreams это может быть проблемой. Все эти библиотеки будут читать весь inputstream. Это потребует больших затрат времени на обнаружение кодировки.

можно ограничить начальную загрузку данных несколькими байтами и выполнить обнаружение кодировки на этих нескольких байтах только.


Если вы не знаете кодировку данных, это не так легко определить, но вы могли бы попробовать использовать библиотека, чтобы угадать это. Кроме того, есть аналогичный вопрос.


насколько я знаю, в этом контексте нет общей библиотеки, подходящей для всех типов проблем. Таким образом, для каждой задачи вы должны протестировать существующие библиотеки и выбрать лучшую, которая удовлетворяет ограничениям вашей проблемы, но часто ни одна из них не подходит. В этих случаях вы можете написать свой собственный детектор кодирования! Как я уже писал ...

Я написал инструмент meta java для обнаружения кодировки кодировок HTML веб-страниц, используя IBM ICU4j и Mozilla JCharDet в качестве встроенные компоненты. здесь вы можете найти мой инструмент, пожалуйста, прочитайте раздел README, прежде чем что-либо еще. Также, вы можете найти некоторые основные понятия этой проблемы в моем статьи и в его ссылках.

ниже я предоставил некоторые полезные комментарии, которые я испытал в своей работе:

  • обнаружение кодировки не является надежным процессом, потому что он по существу основан на статистических данных, и то, что на самом деле происходит, гадание не определения
  • icu4j является основным инструментом в этом контексте IBM, imho
  • оба TikaEncodingDetector и Lucene-ICU4j используют icu4j, и их точность не имела значимого отличия от того, что icu4j в моих тестах (не более %1, Как я помню)
  • icu4j гораздо более общий, чем jchardet, icu4j просто немного смещен к кодировкам семейства IBM, в то время как jchardet сильно смещен к utf-8
  • из-за широкое использование UTF-8 в HTML-мире; jchardet-лучший выбор, чем icu4j в целом, но не лучший выбор!
  • icu4j отлично подходит для восточноазиатских кодировок, таких как EUC-KR, EUC-JP, SHIFT_JIS, BIG5 и семейные кодировки GB
  • как icu4j, так и jchardet-Это фиаско в работе с HTML-страницами с кодировками Windows-1251 и Windows-1256. Windows-1251 aka cp1251 широко используется для кириллических языков, таких как русский и Windows-1256 aka cp1256 is широко используется для арабского
  • почти все инструменты обнаружения кодирования используют статистические методы, поэтому точность вывода сильно зависит от размера и содержимого ввода
  • некоторые кодировки по существу одинаковы только с частичными различиями, поэтому в некоторых случаях угаданная или обнаруженная кодировка может быть ложной, но в то же время истинной! Что касается Windows-1252 и ISO-8859-1. (см. последний абзац в разделе 5.2 бумага)

для файлов ISO8859_1 существует не простой способ отличить их от ASCII. Для Unicode файлов, однако, как правило, можно обнаружить это на основе первых нескольких байтов файла.

UTF-8 и UTF-16 файлы включают в себя Метка Порядка Байтов (BOM) в самом начале файла. BOM-это пространство с нулевой шириной.

к сожалению, по историческим причинам, Java не обнаруживает это автоматически. Такие программы, как Notepad, проверят спецификацию и используйте соответствующую кодировку. Используя unix или Cygwin, вы можете проверить спецификацию с помощью команды file. Например:

$ file sample2.sql 
sample2.sql: Unicode text, UTF-16, big-endian

для Java я предлагаю вам проверить этот код, который обнаружит общие форматы файлов и выберет правильную кодировку: Как прочитать файл и автоматически указать правильную кодировку


альтернативой TikaEncodingDetector является использование Тика AutoDetectReader.

Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();

в простой Java:

final String[] encodings = { "US-ASCII", "ISO-8859-1", "UTF-8", "UTF-16BE", "UTF-16LE", "UTF-16" };

List<String> lines;

for (String encoding : encodings) {
    try {
        lines = Files.readAllLines(path, Charset.forName(encoding));
        for (String line : lines) {
            // do something...
        }
        break;
    } catch (IOException ioe) {
        System.out.println(encoding + " failed, trying next.");
    }
}

этот подход будет пробовать кодировки один за другим, пока один не работает или у нас не закончится. (BTW my encodings list имеет только те элементы, потому что они являются реализациями charsets, требуемыми на каждой платформе Java,https://docs.oracle.com/javase/9/docs/api/java/nio/charset/Charset.html)


можете ли вы выбрать соответствующий набор символов в конструктор:

new InputStreamReader(new FileInputStream(in), "ISO8859_1");