Quale libreria usare?
Al momento della stesura di questo documento, sono tre le librerie che emergono:
Non includo Apache Any23 perché utilizza ICU4j 3.4 sotto il cofano.
Come dire quale ha rilevato il set di caratteri giusto (o il più vicino possibile)?
È impossibile certificare il set di caratteri rilevato da ciascuna delle librerie precedenti. Tuttavia, è possibile chiedere loro a turno e segnare la risposta restituita.
Come segnare la risposta restituita?
A ogni risposta può essere assegnato un punto. Più punti ha una risposta, maggiore è la sicurezza del set di caratteri rilevato. Questo è un metodo di punteggio semplice. Puoi elaborare altri.
C'è qualche codice di esempio?
Ecco uno snippet completo che implementa la strategia descritta nelle righe precedenti.
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() + " }";
}
Miglioramenti:
ilguessEncoding
metodo legge interamente l'inputstream. Per flussi di input di grandi dimensioni questo può essere un problema. Tutte queste librerie leggerebbero l'intero inputstream. Ciò implicherebbe un grande dispendio di tempo per il rilevamento del set di caratteri.
È possibile limitare il caricamento iniziale dei dati a pochi byte ed eseguire il rilevamento dei set di caratteri solo su quei pochi byte.