Какую библиотеку использовать?
На момент написания этой статьи появилось три библиотеки:
Я не включаю Apache Any23, потому что он использует ICU4j 3.4 под капотом.
Как определить, какая из них обнаружила правильную кодировку (или как можно более близкую)?
Невозможно сертифицировать кодировку, обнаруженную каждой из указанных библиотек. Однако можно задать их по очереди и оценить полученный ответ.
Как оценить полученный ответ?
Каждому ответу можно присвоить один балл. Чем больше очков в ответе, тем больше уверенности в обнаруженной кодировке. Это простой метод подсчета очков. Вы можете уточнить другие.
Есть ли образец кода?
Вот полный фрагмент, реализующий стратегию, описанную в предыдущих строках.
public static String guessEncoding(InputStream input) throws IOException {
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();
Map<String, int[]> encodingsScores = new HashMap<>();
updateEncodingsScores(encodingsScores, new CharsetToolkit(data).guessEncoding().displayName());
CharsetDetector charsetDetector = new CharsetDetector();
charsetDetector.setText(data);
charsetDetector.enableInputFilter(true);
CharsetMatch cm = charsetDetector.detect();
if (cm != null) {
updateEncodingsScores(encodingsScores, cm.getName());
}
UniversalDetector universalDetector = new UniversalDetector(null);
universalDetector.handleData(data, 0, data.length);
universalDetector.dataEnd();
String encodingName = universalDetector.getDetectedCharset();
if (encodingName != null) {
updateEncodingsScores(encodingsScores, encodingName);
}
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();
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
метод считывает InputStream полностью. Для больших входных потоков это может быть проблемой. Все эти библиотеки будут читать весь поток ввода. Это потребовало бы больших затрат времени на определение кодировки.
Можно ограничить начальную загрузку данных несколькими байтами и выполнить определение кодировки только на этих нескольких байтах.