web-dev-qa-db-fra.com

Java: Comment déterminer le codage correct du jeu de caractères d'un flux

En ce qui concerne le fil suivant: Application Java: impossible de lire correctement le fichier codé en iso-8859-1

Quel est le meilleur moyen de déterminer par programmation le codage correct du jeu de caractères d'un flux d'entrée/fichier?

J'ai essayé d'utiliser les éléments suivants:

File in =  new File(args[0]);
InputStreamReader r = new InputStreamReader(new FileInputStream(in));
System.out.println(r.getEncoding());

Mais sur un fichier dont je sais qu’il est codé ISO8859_1, le code ci-dessus génère un code ASCII, qui n’est pas correct et ne me permet pas de restituer correctement le contenu du fichier sur la console.

120
Joel

J'ai utilisé cette bibliothèque, similaire à jchardet pour détecter le codage en Java: http://code.google.com/p/juniversalchardet/

65
Luciano Fiandesio

Vous ne pouvez pas déterminer le codage d'un flux d'octets arbitraire. C'est la nature des encodages. Un codage signifie un mappage entre une valeur d'octet et sa représentation. Donc, chaque encodage "pourrait" être le bon.

La méthode getEncoding () retournera le codage qui a été configuré (lisez le JavaDoc ) pour le flux. Il ne devinera pas l'encodage pour vous.

Certains flux vous indiquent quel encodage a été utilisé pour les créer: XML, HTML. Mais pas un flux d'octets arbitraire.

Quoi qu'il en soit, vous pouvez essayer de deviner vous-même un encodage si vous devez le faire. Chaque langue a une fréquence commune pour chaque caractère. En anglais, le caractère apparaît très souvent mais ê apparaîtra très très rarement. Dans un flux ISO-8859-1, il n'y a généralement pas de caractères 0x00. Mais un flux UTF-16 en contient beaucoup.

Ou: vous pouvez demander à l'utilisateur. J'ai déjà vu des applications qui vous présentent un extrait du fichier sous différents encodages et vous demandent de sélectionner celui "correct".

95
Eduard Wirch

vérifiez ceci: http://site.icu-project.org/ (icu4j) ils ont des bibliothèques pour détecter les jeux de caractères d'IOStream pourraient être simples comme ceci:

BufferedInputStream bis = new BufferedInputStream(input);
CharsetDetector cd = new CharsetDetector();
cd.setText(bis);
CharsetMatch cm = cd.detect();

if (cm != null) {
   reader = cm.getReader();
   charset = cm.getName();
}else {
   throw new UnsupportedCharsetException()
}
32
user345883

Voici mes favoris:

TikaEncodingDetector

Dépendance:

<dependency>
  <groupId>org.Apache.any23</groupId>
  <artifactId>Apache-any23-encoding</artifactId>
  <version>1.1</version>
</dependency>

Échantillon:

public static Charset guessCharset(InputStream is) throws IOException {
  return Charset.forName(new TikaEncodingDetector().guessEncoding(is));    
}

GuessEncoding

Dépendance:

<dependency>
  <groupId>org.codehaus.guessencoding</groupId>
  <artifactId>guessencoding</artifactId>
  <version>1.4</version>
  <type>jar</type>
</dependency>

Échantillon:

  public static Charset guessCharset2(File file) throws IOException {
    return CharsetToolkit.guessEncoding(file, 4096, StandardCharsets.UTF_8);
  }
23
Benny Neugebauer

Vous pouvez certainement valider le fichier d'un jeu de caractères particulier en décodant avec un CharsetDecoder et en surveillant les erreurs "malformed-input" ou "mappable-character". Bien sûr, cela ne vous dit que si un jeu de caractères est faux; cela ne vous dit pas si c'est correct. Pour cela, vous avez besoin d’une base de comparaison permettant d’évaluer les résultats décodés, par ex. Savez-vous à l'avance si les caractères sont limités à un sous-ensemble ou si le texte respecte un format strict? L'essentiel est que la détection de jeu de caractères est une conjecture sans aucune garantie.

13
Zach Scrivena

Quelle bibliothèque utiliser?

Au moment d'écrire ces lignes, trois bibliothèques ont émergé: 

Je n'inclus pas Apache Any23 car il utilise ICU4j 3.4 sous le capot.

Comment savoir lequel a détecté le jeu de caractères right (ou aussi proche que possible)?

Il est impossible de certifier le jeu de caractères détecté par chacune des bibliothèques ci-dessus. Cependant, il est possible de leur demander à tour de rôle et de noter la réponse renvoyée.

Comment marquer la réponse renvoyée?

Chaque réponse peut se voir attribuer un point. Plus le nombre de points d'une réponse est élevé, plus le jeu de caractères détecté est fiable. Ceci est une méthode de notation simple. Vous pouvez élaborer d'autres.

Y a-t-il un exemple de code?

Voici un extrait complet mettant en œuvre la stratégie décrite dans les lignes précédentes.

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() + " }";
}

Améliorations: La méthode guessEncoding lit entièrement le flux d'entrée. Pour les grands flux d'entrée, cela peut être une préoccupation. Toutes ces bibliothèques liraient tout le flux d'entrée. Cela impliquerait une consommation de temps importante pour la détection du jeu de caractères.

Il est possible de limiter le chargement initial des données à quelques octets et d'effectuer la détection de jeu de caractères uniquement sur ces quelques octets.

9
Stephan

Les bibliothèques ci-dessus sont de simples détecteurs de nomenclature qui ne fonctionnent bien entendu que s'il existe une nomenclature au début du fichier. Jetez un coup d’œil à http://jchardet.sourceforge.net/ qui scanne le texte 

7
Lorrat

Si vous utilisez ICU4J ( http://icu-project.org/apiref/icu4j/ )

Voici mon code:

            String charset = "ISO-8859-1"; //Default chartset, put whatever you want

            byte[] fileContent = null;
            FileInputStream fin = null;

            //create FileInputStream object
            fin = new FileInputStream(file.getPath());

            /*
             * Create byte array large enough to hold the content of the file.
             * Use File.length to determine size of the file in bytes.
             */
            fileContent = new byte[(int) file.length()];

            /*
             * To read content of the file in byte array, use
             * int read(byte[] byteArray) method of Java FileInputStream class.
             *
             */
            fin.read(fileContent);

            byte[] data =  fileContent;

            CharsetDetector detector = new CharsetDetector();
            detector.setText(data);

            CharsetMatch cm = detector.detect();

            if (cm != null) {
                int confidence = cm.getConfidence();
                System.out.println("Encoding: " + cm.getName() + " - Confidence: " + confidence + "%");
                //Here you have the encode name and the confidence
                //In my case if the confidence is > 50 I return the encode, else I return the default value
                if (confidence > 50) {
                    charset = cm.getName();
                }
            }

N'oubliez pas de mettre tous les prises d'essais en ont besoin.

J'espère que cela fonctionne pour vous.

5
ssamuel68

J'ai trouvé une belle bibliothèque tierce capable de détecter le codage réel: http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding

Je ne l'ai pas testé de manière approfondie mais cela semble fonctionner.

5
falcon

Autant que je sache, il n'y a pas de bibliothèque générale dans ce contexte qui convienne à tous les types de problèmes. Par conséquent, pour chaque problème, vous devez tester les bibliothèques existantes et choisir la meilleure qui réponde aux contraintes de votre problème, mais aucune d’entre elles n’est appropriée. Dans ces cas, vous pouvez écrire votre propre détecteur de codage! Comme je l'ai écrit ...

J'ai écrit un outil méta-Java pour détecter le codage de jeux de caractères de pages Web HTML, en utilisant IBM ICU4j et Mozilla JCharDet en tant que composants intégrés. Ici vous pouvez trouver mon outil, veuillez lire la section README avant toute chose. Vous pouvez également trouver quelques concepts de base de ce problème dans mon papier et dans ses références. 

Ci-dessous, j’ai fourni quelques commentaires utiles que j’ai connus dans mon travail: 

  • La détection de jeu de caractères n'est pas un processus infaillible, car elle est essentiellement basée sur des données statistiques et ce qui se passe réellement est suppose que n'est pas détectant
  • icu4j est le principal outil utilisé par IBM dans ce contexte.
  • TikaEncodingDetector et Lucene-ICU4j utilisent tous deux icu4j et leur précision n’a pas de différence significative par rapport à icu4j dans mes tests (au plus% 1, si je me souviens bien)
  • icu4j est beaucoup plus général que jchardet, icu4j est juste un peu biaisé par les encodages de la famille IBM tandis que jchardet est fortement biaisé en utf-8
  • En raison de l'utilisation répandue de UTF-8 dans HTML-world; jchardet est un meilleur choix que icu4j dans l’ensemble, mais n’est pas le meilleur choix!
  • icu4j convient parfaitement aux codages spécifiques à l'Asie de l'Est tels que les codages EUC-KR, EUC-JP, SHIFT_JIS, BIG5 et de la famille GB.
  • Icu4j et jchardet sont tous deux débordés par le traitement des pages HTML avec les codages Windows-1251 et Windows-1256. Windows-1251, cp1251, est largement utilisé pour les langues cyrilliques comme le russe et Windows-1256, cp1256, est largement utilisé pour l'arabe. 
  • Presque tous les outils de détection d'encodage utilisent des méthodes statistiques, donc la précision de la sortie dépend fortement de la taille et du contenu de l'entrée. 
  • Certains encodages sont essentiellement les mêmes avec des différences partielles, donc dans certains cas l'encodage deviné ou détecté peut être faux mais en même temps être vrai! En ce qui concerne Windows-1252 et ISO-8859-1. (voir le dernier paragraphe de la section 5.2 de mon document)
4
faghani

Si vous ne connaissez pas l'encodage de vos données, ce n'est pas si facile à déterminer, mais vous pouvez utiliser une bibliothèque pour le deviner . En outre, il y a une question similaire .

4
Fabian Steeg

Pour les fichiers ISO8859_1, il n’est pas facile de les distinguer de l’ASCII. Cependant, pour les fichiers Unicode, on peut généralement le détecter en se basant sur les premiers octets du fichier.

Les fichiers UTF-8 et UTF-16 incluent un Byte Order Mark (BOM) au tout début du fichier. La nomenclature est un espace insécable de largeur zéro. 

Malheureusement, pour des raisons historiques, Java ne le détecte pas automatiquement. Des programmes comme Notepad vérifieront la nomenclature et utiliseront l'encodage approprié. Avec Unix ou Cygwin, vous pouvez vérifier la nomenclature à l'aide de la commande de fichier. Par exemple:

$ file sample2.sql 
sample2.sql: Unicode text, UTF-16, big-endian

Pour Java, je vous suggère de consulter ce code, qui détectera les formats de fichier courants et sélectionnera le bon codage: Comment lire un fichier et spécifier automatiquement le bon codage

2
brianegge

Une alternative à TikaEncodingDetector est d'utiliser Tika AutoDetectReader .

Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
1
Nolf

En clair Java:

final String[] encodings = { "US-ASCII", "ISO-8859-1", "UTF-8", "UTF-16BE", "UTF-16LE", "UTF-16" };

List<String> lines;

for (String encoding : encodings) {
    try {
        lines = Files.readAllLines(path, Charset.forName(encoding));
        for (String line : lines) {
            // do something...
        }
        break;
    } catch (IOException ioe) {
        System.out.println(encoding + " failed, trying next.");
    }
}

Cette approche essaiera les encodages un par un jusqu'à ce que l'un d'eux fonctionne ou que nous en manquions . (La liste de mes encodages n'a que ces éléments, car ce sont les implémentations de jeux de caractères requises sur toutes les plateformes Java, https: // docs .Oracle.com/javase/9/docs/api/Java/nio/charset/Charset.html )

0
Andres