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

Что касается следующего потока: Приложение 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, что неверно, и не позволяет мне корректно отображать содержимое файла обратно на консоль.

Ответ 2

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

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

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

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

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

Ответ 3

проверьте это: 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()
}

Ответ 4

Вот мои фавориты:

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

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

Файлы UTF-8 и UTF-16 включают в себя "Знак порядка байтов" (BOM) в самом начале файла. Спецификация представляет собой неразрывное пространство с нулевой шириной.

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

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

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

Ответ 10

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

Не забудьте поставить все, чтобы попытаться поймать его.

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

Ответ 11

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

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

Я не включаю 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 полностью считывает входной поток. Для больших входных потоков это может быть проблемой. Все эти библиотеки будут читать весь входной поток. Это означало бы значительное потребление времени для обнаружения кодировки.

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

Ответ 12

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

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

Ответ 13

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

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

Ответ 14

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

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