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.
J'ai utilisé cette bibliothèque, similaire à jchardet pour détecter le codage en Java: http://code.google.com/p/juniversalchardet/
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".
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()
}
Voici mes favoris:
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));
}
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);
}
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.
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.
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.
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.
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.
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
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.
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.
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:
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 .
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
Une alternative à TikaEncodingDetector est d'utiliser Tika AutoDetectReader .
Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
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 )