online - java acentos utf-8
Java: cómo determinar la codificación de charset correcta de una secuencia (14)
Con referencia al siguiente hilo: Aplicación Java: no se puede leer el archivo codificado iso-8859-1 correctamente
¿Cuál es la mejor manera de determinar programáticamente la codificación de juego de caracteres correcta de una ruta de entrada / archivo?
He intentado usar lo siguiente:
File in = new File(args[0]);
InputStreamReader r = new InputStreamReader(new FileInputStream(in));
System.out.println(r.getEncoding());
Pero en un archivo que sé que está codificado con ISO8859_1, el código anterior produce ASCII, que no es correcto, y no me permite volver a procesar correctamente el contenido del archivo en la consola.
¿Qué biblioteca usar?
A partir de este escrito, son tres bibliotecas que emergen:
No incluyo Apache Any23 porque usa ICU4j 3.4 debajo del capó.
¿Cómo saber cuál ha detectado el juego de caracteres correcto (o lo más cerca posible)?
Es imposible certificar el juego de caracteres detectado por cada biblioteca de arriba. Sin embargo, es posible preguntarles a su vez y calificar la respuesta devuelta.
Cómo anotar la respuesta devuelta?
A cada respuesta se le puede asignar un punto. Cuantos más puntos tenga una respuesta, más confianza tendrá el juego de caracteres detectado. Este es un método de puntuación simple. Puedes elaborar otros.
¿Hay algún código de muestra?
Aquí hay un fragmento completo que implementa la estrategia descrita en las líneas anteriores.
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() + " }";
}
Mejoras: El método guessEncoding
lee el inputstream por completo. Para grandes flujos de entrada esto puede ser una preocupación. Todas estas bibliotecas leerían todo el flujo de entrada. Esto implicaría un gran consumo de tiempo para detectar el juego de caracteres.
Es posible limitar la carga de datos inicial a unos pocos bytes y realizar la detección de conjunto de caracteres solo en esos pocos bytes.
¿Puedes elegir el juego de caracteres apropiado en el Constructor ?
new InputStreamReader(new FileInputStream(in), "ISO8859_1");
Aquí están mis favoritos:
Dependencia:
<dependency>
<groupId>org.apache.any23</groupId>
<artifactId>apache-any23-encoding</artifactId>
<version>1.1</version>
</dependency>
Muestra:
public static Charset guessCharset(InputStream is) throws IOException {
return Charset.forName(new TikaEncodingDetector().guessEncoding(is));
}
Dependencia:
<dependency>
<groupId>org.codehaus.guessencoding</groupId>
<artifactId>guessencoding</artifactId>
<version>1.4</version>
<type>jar</type>
</dependency>
Muestra:
public static Charset guessCharset2(File file) throws IOException {
return CharsetToolkit.guessEncoding(file, 4096, StandardCharsets.UTF_8);
}
Encontré una buena biblioteca de terceros que puede detectar la codificación real: http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding
No lo probé exhaustivamente, pero parece funcionar.
Hasta donde yo sé, no existe una biblioteca general en este contexto que sea adecuada para todo tipo de problemas. Por lo tanto, para cada problema debe probar las bibliotecas existentes y seleccionar la mejor que satisfaga las limitaciones de su problema, pero a menudo ninguna de ellas es adecuada. ¡En estos casos, puede escribir su propio detector de codificación! Como he escrito ...
He escrito una herramienta meta java para detectar la codificación de conjunto de caracteres de páginas web HTML, utilizando IBM ICU4j y Mozilla JCharDet como componentes incorporados. Here puede encontrar mi herramienta, lea la sección LÉAME antes que nada. Además, puedes encontrar algunos conceptos básicos de este problema en mi paper y en sus referencias.
A continuación, proporcioné algunos comentarios útiles que he experimentado en mi trabajo:
- La detección de Charset no es un proceso infalible, ya que se basa esencialmente en datos estadísticos y lo que realmente sucede es adivinar que no se detectan
- icu4j es la herramienta principal en este contexto de IBM,
- Tanto TikaEncodingDetector como Lucene-ICU4j están usando icu4j y su precisión no tuvo una diferencia significativa con respecto a la icu4j en mis pruebas (a lo sumo% 1, según recuerdo)
- icu4j es mucho más general que jchardet, icu4j está un poco sesgado a las codificaciones de la familia de IBM, mientras que jchardet está fuertemente sesgado a utf-8
- Debido al uso generalizado de UTF-8 en el mundo HTML; jchardet es una mejor opción que icu4j en general, ¡pero no es la mejor opción!
- icu4j es ideal para codificaciones específicas del este de Asia como EUC-KR, EUC-JP, SHIFT_JIS, BIG5 y las codificaciones de la familia GB
- Tanto icu4j como jchardet son un desastre al tratar con páginas HTML con codificaciones Windows-1251 y Windows-1256. Windows-1251 también conocido como cp1251 es ampliamente utilizado para idiomas cirílicos como ruso y Windows-1256 también conocido como cp1256 es ampliamente utilizado para árabe
- Casi todas las herramientas de detección de codificación están utilizando métodos estadísticos, por lo que la precisión de la salida depende en gran medida del tamaño y el contenido de la entrada
- Algunas codificaciones son esencialmente las mismas con diferencias parciales, por lo que en algunos casos la codificación detectada o detectada puede ser falsa pero, al mismo tiempo, ¡ser cierta! Como sobre Windows-1252 e ISO-8859-1. (consulte el último párrafo en la sección 5.2 de mi documento)
He utilizado esta biblioteca, similar a jchardet para detectar codificación en Java: http://code.google.com/p/juniversalchardet/
Las libs anteriores son simples detectores de BOM que, por supuesto, solo funcionan si hay una lista de materiales en el comienzo del archivo. Eche un vistazo a jchardet.sourceforge.net que escanea el texto
No puede determinar la codificación de una secuencia de bytes arbitraria. Esta es la naturaleza de las codificaciones. Una codificación significa un mapeo entre un valor de byte y su representación. Entonces, cada codificación "podría" ser la correcta.
El método getEncoding() devolverá la codificación que se configuró (lea getEncoding() ) para la transmisión. No adivinará la codificación para ti.
Algunas secuencias le dicen qué codificación se utilizó para crearlas: XML, HTML. Pero no un flujo de bytes arbitrario.
De todos modos, podrías tratar de adivinar una codificación por tu cuenta si es necesario. Cada idioma tiene una frecuencia común para cada char. En inglés, la letra aparece muy a menudo pero ê aparecerá muy pocas veces. En una secuencia ISO-8859-1 generalmente no hay caracteres 0x00. Pero una transmisión UTF-16 tiene muchos de ellos.
O bien, podría preguntarle al usuario. Ya he visto aplicaciones que le presentan un fragmento del archivo en diferentes codificaciones y le piden que seleccione el "correcto".
Para archivos ISO8859_1, no hay una manera fácil de distinguirlos de ASCII. Para los archivos Unicode, sin embargo, uno generalmente puede detectar esto en función de los primeros bytes del archivo.
Los archivos UTF-8 y UTF-16 incluyen una Marca de Orden de Byte (BOM) al principio del archivo. La lista de materiales es un espacio sin ruptura de ancho cero.
Desafortunadamente, por razones históricas, Java no detecta esto automáticamente. Programas como el Bloc de notas comprobarán la lista de materiales y utilizarán la codificación adecuada. Usando Unix o Cygwin, puede verificar la lista de materiales con el comando de archivo. Por ejemplo:
$ file sample2.sql
sample2.sql: Unicode text, UTF-16, big-endian
Para Java, le sugiero que consulte este código, que detectará los formatos de archivo comunes y seleccionará la codificación correcta: Cómo leer un archivo y especificar automáticamente la codificación correcta
Si no conoce la codificación de sus datos, no es tan fácil de determinar, pero podría tratar de usar una http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding . Además, hay una pregunta similar .
Si usa ICU4J ( http://icu-project.org/apiref/icu4j/ )
Aquí está mi código:
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();
}
}
Recuerde poner todo el try catch necesario.
Espero que esto funcione para ti.
Sin duda puede validar el archivo para un juego de caracteres en particular decoding con un CharsetDecoder
y vigilando los errores de "entrada mal formada" o "caracteres no configurables". Por supuesto, esto solo te dice si un juego de caracteres está mal; no te dice si es correcto. Para eso, necesita una base de comparación para evaluar los resultados decodificados, por ejemplo, ¿sabe de antemano si los caracteres están restringidos a algún subconjunto, o si el texto se adhiere a algún formato estricto? La conclusión es que la detección de juego de caracteres es una suposición sin garantías.
Una alternativa a TikaEncodingDetector es usar Tika AutoDetectReader .
Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
mira esto: http://site.icu-project.org/ (icu4j) tienen bibliotecas para detectar charset de IOStream podrían ser simples de esta manera:
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()
}