Qual biblioteca usar?
No momento da redação deste artigo, são três as bibliotecas que emergem:
Não incluo Apache Any23 porque ele usa o ICU4j 3.4 sob o capô.
Como saber qual deles detectou o direito caracteres (ou o mais próximo possível)?
É impossível certificar o conjunto de caracteres detectado por cada uma das bibliotecas acima. No entanto, é possível perguntar por vez e pontuar a resposta retornada.
Como pontuar a resposta retornada?
Cada resposta pode ser atribuída a um ponto. Quanto mais pontos uma resposta tiver, mais confiança terá o conjunto de caracteres detectado. Este é um método simples de pontuação. Você pode elaborar outros.
Existe algum código de exemplo?
Aqui está um trecho completo implementando a estratégia descrita nas linhas 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() + " }";
}
Melhorias:
OguessEncoding
método lê o fluxo de entrada completamente. Para fluxos de entrada grandes, isso pode ser uma preocupação. Todas essas bibliotecas liam todo o fluxo de entrada. Isso implicaria um grande consumo de tempo para detectar o conjunto de caracteres.
É possível limitar o carregamento inicial de dados a alguns bytes e executar a detecção do conjunto de caracteres apenas nesses poucos bytes.