Java: comment déterminer l'encodage correct d'un charset d'un flux

en ce qui concerne le filetage suivant: Java App: impossible de lire correctement le fichier codé iso-8859-1

Quelle est la meilleure façon de déterminer de façon programmatique le codage correct d'un jeu de caractères d'un fichier/inputstream ?

j'ai essayé d'utiliser ce qui suit:

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 encodé avec ISO8859_1 le code ci-dessus donne ASCII, ce qui n'est pas correct, et ne permettez-moi de restituer correctement le contenu du fichier à la console.

114
demandé sur Community 2009-01-31 18:34:10

15 réponses

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

63
répondu Luciano Fiandesio 2011-01-19 13:44:36

vous ne pouvez pas déterminer le codage d'un flux d'octets arbitraire. C'est la nature des encodages. Un codage de correspondance entre une valeur d'octet et de sa représentation. Ainsi, chaque encodage "pourrait" être le droit.

la méthode getEncoding () retournera l'encodage qui a été configuré (lire le JavaDoc ) pour le flux. Il ne pense à l'encodage pour vous.

certains ruisseaux vous disent le codage a été utilisé pour les créer: XML, HTML. Mais pas de l'arbitraire d'un flux d'octets.

de toute façon, vous pourriez essayer de deviner un encodage sur votre propre si vous avez. Chaque langue a une fréquence commune pour chaque char. En anglais le caractère apparaît très souvent, mais ê apparaîtra 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 a beaucoup.

Ou: vous pourriez demander à l'utilisateur. J'ai déjà vu des applications qui vous présenter un extrait du fichier dans les différents encodages et vous demandera de sélectionner le "bon".

92
répondu Eduard Wirch 2014-03-10 19:51:19

regardez ça: http://site.icu-project.org / (icu4j) ils ont des bibliothèques pour détecter le charset de IOStream

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()
}
31
répondu user345883 2011-12-17 00:41:15

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);
  }
21
répondu Benny Neugebauer 2014-11-30 12:48:37

vous pouvez certainement valider le fichier pour un jeu de caractères particulier par décodage il avec un CharsetDecoder et attention aux erreurs" malformed-input "ou" unmappable-character". Bien sûr, cela ne vous dit que si un jeu de caractères est erroné; il ne vous dit pas si c'est correct. Pour cela, vous avez besoin d'une base de comparaison pour évaluer les résultats décodés, par exemple savez-vous à l'avance si les caractères sont limités à certains sous-ensemble, ou si le texte respecte strictement format? En fin de compte, la détection du jeu de caractères est une conjecture sans aucune garantie.

12
répondu Zach Scrivena 2009-02-01 07:44:18

les libs ci-dessus sont des détecteurs BOM simples qui ne fonctionnent bien sûr que s'il y a un BOM au début du fichier. Regardez http://jchardet.sourceforge.net / qui scanne le texte

7
répondu Lorrat 2010-02-15 11:53:01

j'ai trouvé une bibliothèque tiers agréable qui peut détecter le codage réel: http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding

je n'ai pas testé beaucoup, mais il semble fonctionner.

5
répondu falcon 2010-01-07 09:04:04

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 tout le Try catch besoin.

j'espère que cela fonctionne pour vous.

5
répondu ssamuel68 2013-04-04 21:01:42

quelle bibliothèque utiliser?

à partir de cette Écriture, ce sont trois bibliothèques qui émergent:

je ne comprend pas Apache Any23 , car elle utilise ICU4j 3.4 sous le capot.

comment dire qui a détecté le jeu de caractères right (ou aussi près 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 retournée.

comment noter la réponse retournée?

chaque réponse peut recevoir un point. Plus les points de réponse sont nombreux, plus la confiance est grande. jeu de caractères. Il s'agit d'une méthode de notation simple. Vous pouvez élaborer d'autres.

y a-t-il un code échantillon?

voici un extrait complet de la mise en œuvre de 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 entrant. Pour les grands entrants, cela peut être préoccupant. Toutes ces bibliothèques liraient tout le flux d'entrées. Cela impliquerait une grande consommation de temps pour détecter le jeu de caractères.

il est possible de limiter le chargement initial de données à quelques octets et d'effectuer la détection de charset sur ces quelques octets seulement.

5
répondu Stephan 2017-07-31 07:58:52

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

4
répondu Fabian Steeg 2017-05-23 12:03:03

autant Que je sache, il n'existe pas de bibliothèque dans ce contexte, d'être adapté à tous les types de problèmes. Ainsi, pour chaque problème, vous devriez tester les bibliothèques existantes et choisir la meilleure qui satisfasse les contraintes de votre problème, mais souvent 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 méta java tool pour détecter l'encodage de charset de pages Web HTML, en utilisant IBM ICU4j et Mozilla Jchardet comme les composants intégrés. ici vous pouvez trouver mon outil, s'il vous plaît lire la section README avant toute autre chose. Aussi, vous pouvez 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 expérimentés dans mon travail:

  • la détection D'un jeu de caractères n'est pas un processus infaillible, car elle est essentiellement fondée sur: les données statistiques et ce qui se passe réellement est deviner pas détecter
  • icu4j est l'outil principal dans ce contexte par IBM, imho
  • les deux TikaEncodingDetector et Lucene-ICU4j utilisent icu4j et leur précision n'avait pas une différence significative par rapport à laquelle le icu4j dans mes tests (au plus %1, comme je me souviens)
  • icu4j est beaucoup plus général que jchardet, icu4j est juste un peu biaisé pour IBM family encodings tandis que jchardet est fortement biaisé pour utf-8
  • en raison de L'utilisation répandue de UTF-8 dans le monde HTML; jchardet est un meilleur choix que icu4j dans l'ensemble, mais n'est pas le meilleur choix!
  • icu4j est idéal pour les encodages spécifiques de L'Asie de l'est comme EUC-KR, EUC-JP, SHIFT_JIS, BIG5 et les encodages de la famille GB
  • icu4j et jchardet sont des débâcles dans le traitement des pages HTML avec Windows-1251 et Windows-1256 encodage. Windows-1251 aka cp1251 est largement utilisé pour les langues basées en Cyrillique comme le russe et Windows-1256 aka cp1256 est largement utilisé pour l'arabe
  • presque tous les outils de détection de codage utilisent des méthodes statistiques, de sorte que l'exactitude 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, de sorte que dans certains cas, l'encodage supposé ou détecté peut être faux, mais à même temps être vrai! Comme pour Windows-1252 et ISO-8859-1. (voir le dernier paragraphe de la section 5.2 du présent document)
3
répondu faghani 2016-06-08 20:29:08

une alternative à TikaEncodingDetector est d'utiliser Tika AutoDetectReader .

Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
1
répondu Nolf 2015-09-03 09:47:17

en Java pur:

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 permettra d'essayer les codages un par un jusqu'à ce que l'on travaille ou nous épuiser. (BTW ma liste d'encodages n'a que ces éléments parce qu'ils sont les implémentations de charsets requises sur chaque plate-forme Java, https://docs.oracle.com/javase/9/docs/api/java/nio/charset/Charset.html )

0
répondu Andres 2018-07-30 14:12:56

pouvez-vous choisir le jeu de caractères approprié dans le constructeur :

new InputStreamReader(new FileInputStream(in), "ISO8859_1");
-11
répondu Kevin 2009-01-31 15:44:08