Convertir cadena en matriz de bytes e invertir en Java

1. Introducción

Con frecuencia necesitamos convertir entre cadenas y matrices de bytes en Java. En este tutorial, examinaremos estas operaciones en detalle.

Primero, veremos varias formas de convertir una cadena en una matriz de bytes . Luego, veremos operaciones similares a la inversa.

2. Conversión de cadena en matriz de bytes

Una cadena se almacena como una matriz de caracteres Unicode en Java. Para convertirlo en una matriz de bytes , traducimos la secuencia de caracteres en una secuencia de bytes. Para esta traducción, usamos una instancia de Charset . Esta clase especifica un mapeo entre una secuencia de Char s y una secuencia de byte s .

Nos referimos al proceso anterior como codificación .

Podemos codificar una cadena en una matriz de bytes en Java de varias formas. Veamos cada uno de ellos en detalle con ejemplos.

2.1. Usando String.getBytes ()

La clase String proporciona tres métodos getBytes sobrecargados para codificar un String en una matriz de bytes :

  • getBytes () : codifica utilizando el juego de caracteres predeterminado de la plataforma
  • getBytes (String charsetName) : codifica utilizando el juego de caracteres con nombre
  • getBytes (Charset charset) : codifica utilizando el juego de caracteres proporcionado

En primer lugar, codifiquemos una cadena utilizando el juego de caracteres predeterminado de la plataforma:

String inputString = "Hello World!"; byte[] byteArrray = inputString.getBytes();

El método anterior depende de la plataforma, ya que utiliza el juego de caracteres predeterminado de la plataforma. Podemos obtener este juego de caracteres llamando a Charset.defaultCharset () .

En segundo lugar, codifiquemos una cadena usando un juego de caracteres con nombre:

@Test public void whenGetBytesWithNamedCharset_thenOK() throws UnsupportedEncodingException { String inputString = "Hello World!"; String charsetName = "IBM01140"; byte[] byteArrray = inputString.getBytes("IBM01140"); assertArrayEquals( new byte[] { -56, -123, -109, -109, -106, 64, -26, -106, -103, -109, -124, 90 }, byteArrray); }

Este método arroja UnsupportedEncodingException si el juego de caracteres nombrado no es compatible.

El comportamiento de las dos versiones anteriores no está definido si la entrada contiene caracteres que no son compatibles con el juego de caracteres. Por el contrario, la tercera versión usa la matriz de bytes de reemplazo predeterminada del juego de caracteres para codificar la entrada no admitida.

A continuación, llamemos a la tercera versión del método getBytes () y pasemos una instancia de Charset:

@Test public void whenGetBytesWithCharset_thenOK() { String inputString = "Hello ਸੰਸਾਰ!"; Charset charset = Charset.forName("ASCII"); byte[] byteArrray = inputString.getBytes(charset); assertArrayEquals( new byte[] { 72, 101, 108, 108, 111, 32, 63, 63, 63, 63, 63, 33 }, byteArrray); }

Aquí, estamos usando el método de fábrica Charset.forName para obtener una instancia del Charset . Este método genera una excepción en tiempo de ejecución si el nombre del juego de caracteres solicitado no es válido. También lanza una excepción de tiempo de ejecución si el juego de caracteres es compatible con la JVM actual.

Sin embargo, se garantiza que algunos juegos de caracteres estarán disponibles en todas las plataformas Java. La clase StandardCharsets define constantes para estos juegos de caracteres.

Finalmente, codifiquemos usando uno de los conjuntos de caracteres estándar:

@Test public void whenGetBytesWithStandardCharset_thenOK() { String inputString = "Hello World!"; Charset charset = StandardCharsets.UTF_16; byte[] byteArrray = inputString.getBytes(charset); assertArrayEquals( new byte[] { -2, -1, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 33 }, byteArrray); }

Así, completamos la revisión de las distintas versiones de getBytes . A continuación, veamos el método proporcionado por Charset .

2.2. Usando Charset.encode ()

La clase Charset proporciona encode () , un método conveniente que codifica caracteres Unicode en bytes. Este método siempre reemplaza la entrada no válida y los caracteres no asignables utilizando la matriz de bytes de reemplazo predeterminada del juego de caracteres.

Usemos el método de codificación para convertir una cadena en una matriz de bytes :

@Test public void whenEncodeWithCharset_thenOK() { String inputString = "Hello ਸੰਸਾਰ!"; Charset charset = StandardCharsets.US_ASCII; byte[] byteArrray = charset.encode(inputString).array(); assertArrayEquals( new byte[] { 72, 101, 108, 108, 111, 32, 63, 63, 63, 63, 63, 33 }, byteArrray); }

Como podemos ver arriba, los caracteres no admitidos se han reemplazado con el byte de reemplazo predeterminado 63 del juego de caracteres .

Los enfoques utilizados hasta ahora utilizan la clase CharsetEncoder internamente para realizar la codificación. Examinemos esta clase en la siguiente sección.

2.3. CharsetEncoder

CharsetEncoder transforma los caracteres Unicode en una secuencia de bytes para un juego de caracteres determinado . Además, proporciona un control detallado sobre el proceso de codificación .

Usemos esta clase para convertir una cadena en una matriz de bytes :

@Test public void whenUsingCharsetEncoder_thenOK() throws CharacterCodingException { String inputString = "Hello ਸੰਸਾਰ!"; CharsetEncoder encoder = StandardCharsets.US_ASCII.newEncoder(); encoder.onMalformedInput(CodingErrorAction.IGNORE) .onUnmappableCharacter(CodingErrorAction.REPLACE) .replaceWith(new byte[] { 0 }); byte[] byteArrray = encoder.encode(CharBuffer.wrap(inputString)) .array(); assertArrayEquals( new byte[] { 72, 101, 108, 108, 111, 32, 0, 0, 0, 0, 0, 33 }, byteArrray); }

Aquí, estamos creando una instancia de CharsetEncoder llamando al método newEncoder en un objeto Charset .

Entonces, estamos especificando las acciones para condiciones de error llamando a la onMalformedInput () y onUnmappableCharacter) ( métodos . Podemos especificar las siguientes acciones:

  • IGNORAR - eliminar la entrada errónea
  • REEMPLAZAR - reemplazar la entrada errónea
  • INFORME: informe del error devolviendo un objeto CoderResult o lanzando una CharacterCodingException

Además, estamos usando el método replaceWith () para especificar la matriz de bytes de reemplazo .

Por lo tanto, completamos la revisión de varios enfoques para convertir una cadena en una matriz de bytes. A continuación, veamos la operación inversa.

3. Conversión de matriz de bytes en cadena

Nos referimos al proceso de convertir una matriz de bytes en una cadena como decodificación . Similar a la codificación, este proceso requiere un juego de caracteres .

Sin embargo, no podemos usar cualquier juego de caracteres para decodificar una matriz de bytes. Deberíamos usar el juego de caracteres que se usó para codificar la Cadena en la matriz de bytes .

Podemos convertir una matriz de bytes en una cadena de muchas formas. Examinemos cada uno de ellos en detalle.

3.1. Usando el constructor de cadenas

The String class has few constructors which take a byte array as input. They are all similar to the getBytes method but work in reverse.

First, let's convert a byte array to String using the platform's default charset:

@Test public void whenStringConstructorWithDefaultCharset_thenOK() { byte[] byteArrray = { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 }; String string = new String(byteArrray); assertNotNull(string); }

Note that we don't assert anything here about the contents of the decoded string. This is because it may decode to something different, depending on the platform's default charset.

For this reason, we should generally avoid this method.

Secondly, let's use a named charset for decoding:

@Test public void whenStringConstructorWithNamedCharset_thenOK() throws UnsupportedEncodingException { String charsetName = "IBM01140"; byte[] byteArrray = { -56, -123, -109, -109, -106, 64, -26, -106, -103, -109, -124, 90 }; String string = new String(byteArrray, charsetName); assertEquals("Hello World!", string); }

This method throws an exception if the named charset is not available on the JVM.

Thirdly, let's use a Charset object to do decoding:

@Test public void whenStringConstructorWithCharSet_thenOK() { Charset charset = Charset.forName("UTF-8"); byte[] byteArrray = { 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 }; String string = new String(byteArrray, charset); assertEquals("Hello World!", string); }

Finally, let's use a standard Charset for the same:

@Test public void whenStringConstructorWithStandardCharSet_thenOK() { Charset charset = StandardCharsets.UTF_16; byte[] byteArrray = { -2, -1, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 33 }; String string = new String(byteArrray, charset); assertEquals("Hello World!", string); }

So far, we have converted a byte array into a String using the constructor. Let's now look into the other approaches.

3.2. Using Charset.decode()

The Charset class provides the decode() method that converts a ByteBuffer to String:

@Test public void whenDecodeWithCharset_thenOK() { byte[] byteArrray = { 72, 101, 108, 108, 111, 32, -10, 111, 114, 108, -63, 33 }; Charset charset = StandardCharsets.US_ASCII; String string = charset.decode(ByteBuffer.wrap(byteArrray)) .toString(); assertEquals("Hello �orl�!", string); }

Here, the invalid input is replaced with the default replacement character for the charset.

3.3. CharsetDecoder

Todos los enfoques anteriores para decodificar internamente usan la clase CharsetDecoder . Podemos usar esta clase directamente para un control detallado del proceso de decodificación :

@Test public void whenUsingCharsetDecoder_thenOK() throws CharacterCodingException { byte[] byteArrray = { 72, 101, 108, 108, 111, 32, -10, 111, 114, 108, -63, 33 }; CharsetDecoder decoder = StandardCharsets.US_ASCII.newDecoder(); decoder.onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE) .replaceWith("?"); String string = decoder.decode(ByteBuffer.wrap(byteArrray)) .toString(); assertEquals("Hello ?orl?!", string); }

Aquí, reemplazamos las entradas no válidas y los caracteres no admitidos con "?".

Si queremos ser informados en caso de entradas no válidas, podemos cambiar el decodificador como:

decoder.onMalformedInput(CodingErrorAction.REPORT) .onUnmappableCharacter(CodingErrorAction.REPORT)

4. Conclusión

En este artículo, investigamos varias formas de convertir String en una matriz de bytes y viceversa. Debemos elegir el método apropiado según los datos de entrada, así como el nivel de control requerido para las entradas no válidas.

Como de costumbre, el código fuente completo se puede encontrar en GitHub.