Java: Как определить правильную кодировку кодировки потока - PullRequest
124 голосов
/ 31 января 2009

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

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

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

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

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

Ответы [ 15 ]

98 голосов
/ 31 января 2009

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

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

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

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

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

65 голосов
/ 19 января 2011

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

33 голосов
/ 25 октября 2010

проверить это: 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()
}
24 голосов
/ 30 ноября 2014

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

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);
  }
13 голосов
/ 01 февраля 2009

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

10 голосов
/ 03 сентября 2015

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

На момент написания этой статьи появилось три библиотеки:

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

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

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

Как оценить полученный ответ?

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

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

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

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

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

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

7 голосов
/ 15 февраля 2010

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

5 голосов
/ 05 апреля 2013

Если вы используете 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();
                }
            }

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

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

5 голосов
/ 07 января 2010

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

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

4 голосов
/ 12 мая 2016

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

Я написал инструмент мета-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 широко используется для арабского языка
  • Почти все инструменты обнаружения кодирования используют статистические методы, поэтому точность вывода сильно зависит от размера и содержимого ввода
  • Некоторые кодировки по сути одинаковы только с частичными различиями, поэтому в некоторых случаях предполагаемое или обнаруженное кодирование может быть ложным, но в то же время может быть истинным! Как насчет Windows-1252 и ISO-8859-1. (см. последний абзац в разделе 5.2 моей статьи)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...