2010-04-28 20 views
11

¿Cuál es la forma más eficiente de calcular la longitud de bytes de un carácter, teniendo en cuenta la codificación de caracteres? La codificación solo se conocerá durante el tiempo de ejecución. En UTF-8, por ejemplo, los caracteres tienen una longitud de bytes variable, por lo que cada carácter debe determinarse individualmente. En lo que ahora se me ha ocurrido con esto:Manera eficiente de calcular la longitud de bytes de un carácter, según la codificación

char c = getCharSomehow(); 
String encoding = getEncodingSomehow(); 
// ... 
int length = new String(new char[] { c }).getBytes(encoding).length; 

Pero esto es torpe e ineficiente en un bucle desde un new String necesario crear cada vez. No puedo encontrar otras formas más eficientes en la API de Java. Hay un String#valueOf(char), pero según su fuente, básicamente es el mismo que el anterior. Me imagino que esto se puede hacer con operaciones a nivel de bits como desplazamiento de bits, pero eso es mi punto débil y estoy seguro de cómo tomar la codificación en cuenta aquí :)

Si duda de la necesidad de esto, compruebe this topic .


Actualización: la respuesta de @Bkkbrad es técnicamente la más eficiente:

char c = getCharSomehow(); 
String encoding = getEncodingSomehow(); 
CharsetEncoder encoder = Charset.forName(encoding).newEncoder(); 
// ... 
int length = encoder.encode(CharBuffer.wrap(new char[] { c })).limit(); 

Sin embargo, como @Stephen C señalado, hay más problemas con esto. Puede haber, por ejemplo, caracteres combinados/sustitutos que también deben tenerse en cuenta. Pero ese es otro problema que debe resolverse en el paso antes de en este paso.

+0

Utilizando lo anterior tuviste problemas de rendimiento? ¿Siempre quieres usar UTF-8? –

+0

El ejemplo fue de hecho un poco engañoso, pero en realidad la codificación solo se puede determinar durante el tiempo de ejecución. He actualizado la pregunta. Después de todo, esto no parece ser una tarea fácil. – BalusC

+3

esto es completamente incorrecto y también lo es la respuesta de bkkbrad. En realidad, es bastante aterrador ver a tantas personas completamente equivocadas en eso (+1 solo a la respuesta de bkail). Un Java * char * does ** not **, repito ** A JAVA CHAR NO ** representa un personaje más desde Java 1.4/Unicode 3.1. * String.value (char) * y envolviendo "char" * y whatnots son todos métodos de los años noventa. El mundo avanzó y ha pasado mucho tiempo que Unicode tiene más de 65 536 puntos de código. Use "int", saque "char" de su mente porque el carácter de Java está roto sin posibilidad de reparación. \t ♩ \t ♩ \t ♩ \t ♩ – SyntaxT3rr0r

Respuesta

10

Utilice CharsetEncoder y reutilice CharBuffer como entrada y ByteBuffer como salida.

En mi sistema, el siguiente código tarda 25 segundos para codificar 100.000 caracteres individuales:

Charset utf8 = Charset.forName("UTF-8"); 
char[] array = new char[1]; 
for (int reps = 0; reps < 10000; reps++) { 
    for (array[0] = 0; array[0] < 10000; array[0]++) { 
     int len = new String(array).getBytes(utf8).length; 
    } 
} 

Sin embargo, el código siguiente hace lo mismo en menos de 4 segundos:

Charset utf8 = Charset.forName("UTF-8"); 
CharsetEncoder encoder = utf8.newEncoder(); 
char[] array = new char[1]; 
CharBuffer input = CharBuffer.wrap(array); 
ByteBuffer output = ByteBuffer.allocate(10); 
for (int reps = 0; reps < 10000; reps++) { 
    for (array[0] = 0; array[0] < 10000; array[0]++) { 
     output.clear(); 
     input.clear(); 
     encoder.encode(input, output, false); 
     int len = output.position(); 
    } 
} 

Editar : ¿Por qué los enemigos tienen que odiar?

He aquí una solución que se lee de un CharBuffer y realiza un seguimiento de surrogate pairs:

Charset utf8 = Charset.forName("UTF-8"); 
CharsetEncoder encoder = utf8.newEncoder(); 
CharBuffer input = //allocate in some way, or pass as parameter 
ByteBuffer output = ByteBuffer.allocate(10); 

int limit = input.limit(); 
while(input.position() < limit) { 
    output.clear(); 
    input.mark(); 
    input.limit(Math.max(input.position() + 2, input.capacity())); 
    if (Character.isHighSurrogate(input.get()) && !Character.isLowSurrogate(input.get())) { 
     //Malformed surrogate pair; do something! 
    } 
    input.limit(input.position()); 
    input.reset(); 
    encoder.encode(input, output, false); 
    int encodedLen = output.position(); 
} 
+1

Técnicamente, esta es la mejor respuesta hasta el momento (si reemplaza 'position()' por 'limit()'). Esto es de hecho mucho eficiente. – BalusC

+1

@Bkkbrad: Un carácter Java es totalmente inadecuado desde 1993 o más o menos para representar un carácter Unicode, cuando Unicode se movió a 1.1 y tenía más de 65 536 puntos de código. El método a utilizar para obtener un carácter en Java es * CodePointAt (..) * de String que devuelve correctamente * int *. Java * char * es, bueno, completamente roto. (Aquí hay 200 bases de código KLOC y estamos usando Java char, bueno ... ** cero ** veces). – SyntaxT3rr0r

+1

@WizardOfOdds: Agregué una solución para hacer un seguimiento de los pares de sustitución. – Bkkbrad

3

Es posible que un esquema de codificación pueda codificar un carácter dado como un número variable de bytes, dependiendo de lo que ocurra antes y después de él en la secuencia de caracteres. La longitud de bytes que obtienes al codificar un solo carácter. Cadena no es, por lo tanto, la respuesta completa.

(Por ejemplo, en teoría, podría recibir un baudot/teletipo caracteres codificados como 4 caracteres cada 3 bytes, o en teoría podría tratar un UTF-16 + un compresor de flujo como un esquema de codificación. Sí, es todo un poco no plausible, pero ...)

+0

Sí, buen punto, los caracteres sustitutos se deben tener en cuenta tarde o temprano. – BalusC

3

Si puede garantizar que la entrada está bien formada UTF-8, entonces no hay razón para encontrar puntos de código en absoluto. Una de las ventajas de UTF-8 es que puede detectar el inicio de un punto de código desde cualquier posición en la cadena. Simplemente busque hacia atrás hasta encontrar un byte tal que (b & 0xc0)! = 0x80, y haya encontrado otro carácter. Como un punto de código codificado en UTF-8 es siempre de 6 bytes o menos, puede copiar los bytes intermedios en un búfer de longitud fija.

Editar: Olvidé mencionar, incluso si no sigue esta estrategia, no es suficiente usar un "carácter" Java para almacenar puntos de código arbitrario ya que los valores de punto de código pueden exceder 0xffff. Necesita almacenar puntos de código en un "int".

+0

Muy buen consejo. Lamentablemente, no hay garantía del 100%. – BalusC

+0

@bkail: +1 a usted, ya que es el único en este hilo que menciona que un Java * char * no puede almacenar puntos de código arbitrarios y que * int * se debe usar en su lugar. – SyntaxT3rr0r

1

Probar Charset.forName("UTF-8").encode("string").limit(); Puede ser un poco más eficiente, tal vez no.

+0

Esto todavía requiere un 'String' como entrada. – BalusC

Cuestiones relacionadas