Z jakiej biblioteki skorzystać?
W chwili pisania tego artykułu powstały trzy biblioteki:
Nie dołączam Apache Any23, ponieważ pod maską używa ICU4j 3.4.
Jak sprawdzić, który z nich wykrył właściwy zestaw znaków (lub możliwie najbliższy)?
Nie można poświadczyć zestawu znaków wykrytego przez każdą z powyższych bibliotek. Można jednak zapytać ich po kolei i ocenić otrzymaną odpowiedź.
Jak ocenić zwróconą odpowiedź?
Każdej odpowiedzi można przypisać jeden punkt. Im więcej punktów ma odpowiedź, tym większe zaufanie ma wykryty zestaw znaków. To jest prosta metoda punktacji. Możesz rozwinąć innych.
Czy jest jakiś przykładowy kod?
Oto pełny fragment implementujący strategię opisaną w poprzednich wierszach.
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() + " }";
}
Ulepszenia:guessEncoding
metoda odczytuje InputStream całkowicie. W przypadku dużych strumieni wejściowych może to stanowić problem. Wszystkie te biblioteki czytałyby cały strumień wejściowy. Oznaczałoby to duże zużycie czasu na wykrycie zestawu znaków.
Możliwe jest ograniczenie początkowego ładowania danych do kilku bajtów i wykrywanie zestawu znaków tylko na tych kilku bajtach.