¿Qué biblioteca usar?
Al escribir estas líneas, son tres bibliotecas que emergen:
No incluyo Apache Any23 porque usa ICU4j 3.4 debajo del capó.
¿Cómo saber cuál ha detectado el juego de caracteres correcto (o lo más cerca posible)?
Es imposible certificar el conjunto de caracteres detectado por cada biblioteca anterior. Sin embargo, es posible preguntarles por turno y calificar la respuesta devuelta.
¿Cómo calificar la respuesta devuelta?
A cada respuesta se le puede asignar un punto. Cuantos más puntos tenga una respuesta, más confianza tendrá el juego de caracteres detectado. Este es un método de puntuación simple. Puedes elaborar otros.
¿Hay algún código de muestra?
Aquí hay un fragmento completo que implementa la estrategia descrita en las líneas anteriores.
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() + " }";
}
Mejoras:
el guessEncoding
método lee el flujo de entrada por completo. Para grandes flujos de entrada esto puede ser una preocupación. Todas estas bibliotecas leerían todo el flujo de entrada. Esto implicaría un gran consumo de tiempo para detectar el juego de caracteres.
Es posible limitar la carga de datos inicial a unos pocos bytes y realizar la detección de juego de caracteres solo en esos pocos bytes.