2009-05-18 14 views
20

Necesito codificar/decodificar matrices de bytes UTF-16 hacia y desde java.lang.String. Los arreglos de bytes me son dados con un Byte Order Marker (BOM), y necesito un byte codificado con una lista de materiales.¿Cómo codifico/decodifico las matrices de bytes UTF-16LE con una lista de materiales?

Además, debido a que estoy tratando con un cliente/servidor de Microsoft, me gustaría emitir la codificación en little endian (junto con la BOM de LE) para evitar cualquier malentendido. Me doy cuenta de que con el BOM debería funcionar Big Endian, pero no quiero nadar aguas arriba en el mundo de Windows.

A modo de ejemplo, este es un método que codifica una java.lang.String como UTF-16 en Little Endian con una lista de materiales:

public static byte[] encodeString(String message) { 

    byte[] tmp = null; 
    try { 
     tmp = message.getBytes("UTF-16LE"); 
    } catch(UnsupportedEncodingException e) { 
     // should not possible 
     AssertionError ae = 
     new AssertionError("Could not encode UTF-16LE"); 
     ae.initCause(e); 
     throw ae; 
    } 

    // use brute force method to add BOM 
    byte[] utf16lemessage = new byte[2 + tmp.length]; 
    utf16lemessage[0] = (byte)0xFF; 
    utf16lemessage[1] = (byte)0xFE; 
    System.arraycopy(tmp, 0, 
        utf16lemessage, 2, 
        tmp.length); 
    return utf16lemessage; 
} 

¿Cuál es la mejor manera de hacer esto en Java? Lo ideal sería evitar copiar toda la matriz de bytes en una nueva matriz de bytes que tenga asignados dos bytes adicionales al principio.

Lo mismo vale para la decodificación de una cadena tal, pero eso es mucho más fácil mediante el uso de la java.lang.String constructor:

public String(byte[] bytes, 
       int offset, 
       int length, 
       String charsetName) 

Respuesta

27

El "UTF-16" charset nombre siempre se codificará con una lista de materiales y decodificar los datos utilizando gran/pequeño endianness, pero "UnicodeBig" y "UnicodeLittle" son útiles para la codificación en un orden de bytes específico. Use UTF-16LE o UTF-16BE para no BOM - see this post para saber cómo usar "\ uFEFF" para manejar listas de materiales manualmente. Consulte here para nombres canónicos de nombres de cadena de caracteres o (preferiblemente) la clase Charset. También tenga en cuenta que solo un limited subset of encodings son absolutamente necesarios para ser compatibles.

+1

Gracias! Sin embargo, un problema más ... Usar "UTF-16" codifica los datos como Big Endian, que sospecho que no funcionarán bien con los datos de Microsoft (a pesar de que existe la lista de materiales). ¿Alguna forma de codificar UTF-16LE con BOM con Java? Actualizaré mi pregunta para reflejar lo que realmente estaba buscando ... –

+0

Haz clic en el enlace "ver esta publicación" que dio. Básicamente, rellena un carácter \ uFEFF al principio de la cadena y luego codifica a UTF-16LE, y el resultado tendrá una lista de materiales adecuada. –

+0

Use "UnicodeLittle" (suponiendo que su JRE lo admita - ("\ uEFFF" + "mi cadena"). GetBytes ("UTF-16LE") de lo contrario). Aunque me sorprendería que las API de Microsoft esperaran una lista de materiales pero no pudieran manejar datos de Big-Endian, tienden a gustarles usar BOMs más que otras plataformas. Prueba con cadenas vacías: puede obtener matrices vacías si no hay datos. – McDowell

2
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(string.length() * 2 + 2); 
    byteArrayOutputStream.write(new byte[]{(byte)0xFF,(byte)0xFE}); 
    byteArrayOutputStream.write(string.getBytes("UTF-16LE")); 
    return byteArrayOutputStream.toByteArray(); 

EDIT: Al volver a leer su pregunta, veo que prefiere evitar la asignación de matrices dobles por completo. Lamentablemente, la API no te proporciona eso, hasta donde yo sé. (Había un método, pero está en desuso, y no puede especificar la codificación con él).

Escribí lo anterior antes de ver su comentario, creo que la respuesta para usar las clases nio está en el camino correcto. Estaba mirando eso, pero no estoy lo suficientemente familiarizado con la API como para saber cómo se hace eso.

+0

Gracias. Además, lo que me hubiera gustado aquí es no asignar toda la matriz de bytes con string.getBytes ("UTF-16LE"), tal vez envolviendo la transmisión como un InputStream, que fue el punto de mi pregunta anterior: http://stackoverflow.com/questions/837703/how-can-i-get-a-java-io-inputstream-from-a-java-lang-string –

+0

Tenga en cuenta que este código realmente asigna arreglos lo suficientemente grandes para el String tres veces, ya que tiene la matriz interna de ByteArrayOutputStream que se copia en la llamada .toByteArray(). Una forma de volver a asignar solo dos es envolver ByteArrayOutputStream en un OutputStreamWriter y escribir la cadena en ese. Entonces todavía tienes el estado interno de ByteArrayOutputStream y la copia hecha por .toByteArray(), pero no el valor de retorno de .getBytes –

+0

Parece que solo estás intercambiando una matriz de caracteres por una matriz de bytes si haces eso, como delegados de OutputStreamWriter a la clase StreamEncoder, que crea un búfer char [] para recuperar los datos de cadena. La cadena es inmutable, y el tamaño de una matriz es invariable, por lo que la copia parece inevitable. Creo que se supone que nio ayuda con esa doble creación en ByteArrayOutputStream – Yishai

6

En primer lugar, para decodificar puede usar el juego de caracteres "UTF-16"; que detecta automáticamente una lista de materiales inicial. Para codificar UTF-16BE, también puede usar el conjunto de caracteres "UTF-16", que escribirá una lista de materiales apropiada y luego emitirá material de big endian.

Para codificar para little endian con una BOM, no creo que su código actual sea demasiado malo, incluso con la doble asignación (a menos que sus cadenas sean verdaderamente monstruosas). Lo que podría querer hacer si es que no es tratar con una matriz de bytes sino con un ByteBuffer java.nio, y usar la clase java.nio.charset.CharsetEncoder. (Que puede obtener de Charset.forName ("UTF-16LE"). NewEncoder()).

+0

Gracias, buen consejo. –

7

Esta es la forma en que lo hace en NIO:

return Charset.forName("UTF-16LE").encode(message) 
      .put(0, (byte) 0xFF) 
      .put(1, (byte) 0xFE) 
      .array(); 

Se supone que es sin duda más rápido, pero no sé cuántos matrices se hace bajo las sábanas, pero mi comprensión del punto de la API es que se supone que minimiza eso.

+0

Éste realmente no funciona. Las llamadas put (0) y put (1) sobrescriben los primeros dos bytes del ByteBuffer del mensaje codificado. – hopia

0

Esta es una vieja pregunta, pero aún así, no pude encontrar una respuesta aceptable para mi situación. Básicamente, Java no tiene un codificador incorporado para UTF-16LE con una lista de materiales. Y entonces, tiene que implementar su propia implementación.

Esto es lo que terminó con:

private byte[] encodeUTF16LEWithBOM(final String s) { 
    ByteBuffer content = Charset.forName("UTF-16LE").encode(s); 
    byte[] bom = { (byte) 0xff, (byte) 0xfe }; 
    return ByteBuffer.allocate(content.capacity() + bom.length).put(bom).put(content).array(); 
} 
Cuestiones relacionadas