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.
15 réponses
j'ai utilisé cette bibliothèque, similaire à jchardet pour détecter l'encodage 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 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".
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()
}
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 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.
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
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.
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.
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.
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 .
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)
pour les fichiers ISO8859_1, il n'est pas facile de les distinguer des fichiers ASCII. Pour les fichiers Unicode cependant, on peut généralement détecter cela basé sur les premiers octets du fichier.
Les fichiersUTF-8 et UTF-16 incluent un (BOM) au tout début du fichier. Le BOM est un espace sans rupture de largeur nulle.
malheureusement, pour des raisons historiques, Java ne détecte pas cela automatiquement. Programme comme Notepad vérifiera le BOM et utilisera le codage approprié. En utilisant unix ou Cygwin, vous pouvez vérifier le BOM avec la commande file. Par exemple:
$ file sample2.sql
sample2.sql: Unicode text, UTF-16, big-endian
pour Java, je vous suggère de vérifier ce code, qui détectera les formats de fichier communs et sélectionner le codage correct: Comment lire un fichier et spécifier automatiquement le codage correct
une alternative à TikaEncodingDetector est d'utiliser Tika AutoDetectReader .
Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
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 )
pouvez-vous choisir le jeu de caractères approprié dans le constructeur :
new InputStreamReader(new FileInputStream(in), "ISO8859_1");