Conversión de char [] en byte []

84

Me gustaría convertir una matriz de caracteres en una matriz de bytes en Java. ¿Qué métodos existen para realizar esta conversión?

Arun Abraham
fuente

Respuestas:

76
char[] ch = ?
new String(ch).getBytes();

o

new String(ch).getBytes("UTF-8");

para obtener un juego de caracteres no predeterminado.

Actualización: desde Java 7:new String(ch).getBytes(StandardCharsets.UTF_8);

Tarlog
fuente
4
Usar el juego de caracteres predeterminado de la plataforma es incorrecto la mayor parte del tiempo (aplicaciones web).
maaartinus
4
Esta es una solución trivial, debido al uso de un nuevo String, el espacio necesario para la operación se duplica. No funcionará muy bien para entradas extremadamente grandes.
Levent Divilioglu
167

Convertir sin crear Stringobjeto:

import java.nio.CharBuffer;
import java.nio.ByteBuffer;
import java.util.Arrays;

byte[] toBytes(char[] chars) {
  CharBuffer charBuffer = CharBuffer.wrap(chars);
  ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
  byte[] bytes = Arrays.copyOfRange(byteBuffer.array(),
            byteBuffer.position(), byteBuffer.limit());
  Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
  return bytes;
}

Uso:

char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
byte[] bytes = toBytes(chars);
/* do something with chars/bytes */
Arrays.fill(chars, '\u0000'); // clear sensitive data
Arrays.fill(bytes, (byte) 0); // clear sensitive data

La solución está inspirada en la recomendación de Swing para almacenar contraseñas en char []. (Consulte ¿Por qué se prefiere char [] sobre String para las contraseñas? )

Recuerde no escribir datos confidenciales en los registros y asegúrese de que JVM no contenga referencias a ellos.


El código anterior es correcto pero no efectivo. Si no necesita rendimiento pero desea seguridad, puede usarlo. Si la seguridad tampoco es un objetivo, hágalo simplemente String.getBytes. El código anterior no es efectivo si observa la implementación de encodeen JDK. Además, necesita copiar matrices y crear búferes. Otra forma de convertir es en línea todo el código subyacente encode(ejemplo para UTF-8 ):

val xs: Array[Char] = "A ß € 嗨 𝄞 🙂".toArray
val len = xs.length
val ys: Array[Byte] = new Array(3 * len) // worst case
var i = 0; var j = 0 // i for chars; j for bytes
while (i < len) { // fill ys with bytes
  val c = xs(i)
  if (c < 0x80) {
    ys(j) = c.toByte
    i = i + 1
    j = j + 1
  } else if (c < 0x800) {
    ys(j) = (0xc0 | (c >> 6)).toByte
    ys(j + 1) = (0x80 | (c & 0x3f)).toByte
    i = i + 1
    j = j + 2
  } else if (Character.isHighSurrogate(c)) {
    if (len - i < 2) throw new Exception("overflow")
    val d = xs(i + 1)
    val uc: Int = 
      if (Character.isLowSurrogate(d)) {
        Character.toCodePoint(c, d)
      } else {
        throw new Exception("malformed")
      }
    ys(j) = (0xf0 | ((uc >> 18))).toByte
    ys(j + 1) = (0x80 | ((uc >> 12) & 0x3f)).toByte
    ys(j + 2) = (0x80 | ((uc >>  6) & 0x3f)).toByte
    ys(j + 3) = (0x80 | (uc & 0x3f)).toByte
    i = i + 2 // 2 chars
    j = j + 4
  } else if (Character.isLowSurrogate(c)) {
    throw new Exception("malformed")
  } else {
    ys(j) = (0xe0 | (c >> 12)).toByte
    ys(j + 1) = (0x80 | ((c >> 6) & 0x3f)).toByte
    ys(j + 2) = (0x80 | (c & 0x3f)).toByte
    i = i + 1
    j = j + 3
  }
}
// check
println(new String(ys, 0, j, "UTF-8"))

Disculpe por usar el lenguaje Scala. Si tiene problemas para convertir este código a Java, puedo reescribirlo. ¿Qué pasa con el rendimiento? Siempre verifique los datos reales (con JMH, por ejemplo). Este código es muy similar a lo que puede ver en JDK [ 2 ] y Protobuf [ 3 ].

Andrii Nemchenko
fuente
¿No crearía esto un ByteBuffer? ¿Supongo que es menos costoso que un objeto String?
Andi Jay
15
@CrazyJay Creo que este método no almacenaría "caracteres" en String Pool. De esta manera, puede trabajar con datos de contraseña de forma más segura.
Andrii Nemchenko
1
@Cassian Tu método funciona incorrectamente. Lea los detalles aquí stackoverflow.com/a/20604909/355491
Andrii Nemchenko
1
@Prabs No, un carácter UTF-8 toma de 1 a 4 bytes. Incluso un carácter ASCII ocupa 8 bits.
Andrii Nemchenko
1
Este método 'toBytes ()' tiene un efecto secundario importante. Limpia los caracteres de entrada. charBuffer.array () en realidad son los caracteres de entrada. Arrays.fill () en realidad eliminaría la entrada. En muchos casos está bien, pero en ocasiones crea un efecto no deseado.
Guangliang
19

Editar: la respuesta de Andrey se ha actualizado, por lo que lo siguiente ya no se aplica.

La respuesta de Andrey (la más votada en el momento de escribir este artículo) es ligeramente incorrecta. Hubiera agregado esto como comentario, pero no soy lo suficientemente confiable.

En la respuesta de Andrey:

char[] chars = {'c', 'h', 'a', 'r', 's'}
byte[] bytes = Charset.forName("UTF-8").encode(CharBuffer.wrap(chars)).array();

la llamada a array () puede no devolver el valor deseado, por ejemplo:

char[] c = "aaaaaaaaaa".toCharArray();
System.out.println(Arrays.toString(Charset.forName("UTF-8").encode(CharBuffer.wrap(c)).array()));

salida:

[97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 0]

Como puede verse, se ha añadido un byte cero. Para evitar esto, utilice lo siguiente:

char[] c = "aaaaaaaaaa".toCharArray();
ByteBuffer bb = Charset.forName("UTF-8").encode(CharBuffer.wrap(c));
byte[] b = new byte[bb.remaining()];
bb.get(b);
System.out.println(Arrays.toString(b));

salida:

[97, 97, 97, 97, 97, 97, 97, 97, 97, 97]

Como la respuesta también aludía al uso de contraseñas, podría valer la pena borrar la matriz que respalda el ByteBuffer (a la que se accede a través de la función array ()):

ByteBuffer bb = Charset.forName("UTF-8").encode(CharBuffer.wrap(c));
byte[] b = new byte[bb.remaining()];
bb.get(b);
blankOutByteArray(bb.array());
System.out.println(Arrays.toString(b));
djsutho
fuente
¿Podría el \ 0 final ser específico de la implementación? Estoy usando 1.7_51 con netbeans 7.4 y no noto ningún \ 0 final.
@orthopteroid sí, este ejemplo podría ser específico de jvm. Esto se ejecutó con Oracle 1.7.0_45 Linux de 64 bits (desde la memoria). Con la siguiente implementación ( grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… ) obtendrá errores si averageBytesPerChar()devuelve algo distinto de 1 (obtengo 1.1). Por interés, ¿qué sistema operativo / arch está usando? Verifiqué dos veces con Oracle 1.7.0_51 y openjdk 1.7.0_51 y lo encontré roto con 10 caracteres.
djsutho
@Andrey no te preocupes. Tenga buffer.array()en cuenta que en la toBytesfunción aún debe anularse, actualmente solo la copia es.
djsutho
@Andrey He editado mi respuesta para reflejar los cambios.
djsutho
@djsutho Hoy, mi plataforma es windows7x64. Lo siento, no puedo mostrar el código. Estoy usando un código como "System.arraycopy (str.getBytes (" UTF-8 "), 0, stor, 0, used);" ahora.
0
private static byte[] charArrayToByteArray(char[] c_array) {
        byte[] b_array = new byte[c_array.length];
        for(int i= 0; i < c_array.length; i++) {
            b_array[i] = (byte)(0xFF & (int)c_array[i]);
        }
        return b_array;
}
Mate
fuente
-5

Podrías hacer un método:

public byte[] toBytes(char[] data) {
byte[] toRet = new byte[data.length];
for(int i = 0; i < toRet.length; i++) {
toRet[i] = (byte) data[i];
}
return toRet;
}

Espero que esto ayude

Java es genial
fuente
4
Esta respuesta es incorrecta porque los datos de caracteres son Unicode y, como tal, puede haber hasta 4 bytes por carácter (son posibles más, pero en la vida real, solo he encontrado hasta 4). Simplemente tomar un byte de cada carácter solo funcionará para un conjunto de caracteres muy limitado. Lea 'El mínimo absoluto que todo desarrollador de software debe conocer absoluta y positivamente sobre Unicode y conjuntos de caracteres (¡sin excusas!)' En joelonsoftware.com/articles/Unicode.html .
Ilane