it-roy-ru.com

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

120
Joel

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

65
Luciano Fiandesio

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

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

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

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

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

95
Eduard Wirch

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

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

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);
  }
23
Benny Neugebauer

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

13
Zach Scrivena

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

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

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

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

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

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

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

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

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

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

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

9
Stephan

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

7
Lorrat

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

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

Я не проверял это всесторонне, но это, кажется, работает.

5
falcon

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

Я написал инструмент мета-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 моей статьи)
4
faghani

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

4
Fabian Steeg

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

2
brianegge

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

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

В простой 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.");
    }
}

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

0
Andres