Преобразование char [] в byte []

84

Я хотел бы преобразовать массив символов в массив байтов в Java. Какие существуют методы для этого преобразования?

Арун Абрахам
источник

Ответы:

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

или же

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

чтобы получить кодировку не по умолчанию.

Обновление: начиная с Java 7:new String(ch).getBytes(StandardCharsets.UTF_8);

Tarlog
источник
4
В большинстве случаев использование кодировки по умолчанию для платформы неверно (веб-приложения).
maaartinus 01
4
Это тривиальное решение, поскольку при использовании новой строки пространство, необходимое для операции, удваивается. Это не будет работать с очень большими входами.
Левент Дивилиоглу
167

Преобразовать без создания Stringобъекта:

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;
}

Применение:

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

Решение основано на рекомендации Swing хранить пароли в char []. (См. Почему char [] предпочтительнее String для паролей? )

Помните, что не следует записывать конфиденциальные данные в журналы, и убедитесь, что JVM не будет содержать на них никаких ссылок.


Приведенный выше код правильный, но не эффективен. Если вам не нужна производительность, но нужна безопасность, вы можете ее использовать. Если безопасность также не является целью, тогда делайте это просто String.getBytes. Приведенный выше код неэффективен, если вы посмотрите на реализацию encodeв JDK. Кроме того, вам нужно копировать массивы и создавать буферы. Другой способ преобразования - это встроенный весь код encode(например, для 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"))

Извините за использование языка Scala. Если у вас возникнут проблемы с преобразованием этого кода в Java, я могу его переписать. Насчет производительности всегда проверяйте на реальных данных (например, с JMH). Этот код очень похож на тот, что вы видите в JDK [ 2 ] и Protobuf [ 3 ].

Андрей Немченко
источник
Разве это не создало бы ByteBuffer? Я думаю, это дешевле, чем объект String?
Andi Jay
15
@CrazyJay Я считаю, что этот метод не хранит символы в пуле строк. Таким образом, вы можете работать с паролем более безопасно.
Андрей Немченко
1
@Cassian Ваш метод работает некорректно. Подробности читайте здесь stackoverflow.com/a/20604909/355491
Андрей Немченко
1
@Prabs Нет, один символ UTF-8 занимает от 1 до 4 байтов. Даже один символ ASCII занимает 8 бит.
Андрей Немченко
1
Этот метод toBytes () имеет важный побочный эффект. Он стирает входные символы. charBuffer.array () на самом деле является входными символами. Arrays.fill () фактически уничтожит ввод. Во многих случаях это нормально, но иногда это создает нежелательный эффект.
Guangliang
19

Изменить: ответ Андрея был обновлен, поэтому следующее больше не применяется.

Ответ Андрея (наибольшее количество голосов на момент написания) несколько неверен. Я бы добавил это в качестве комментария, но я недостаточно уважаемый.

В ответ Андрея:

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

вызов array () может не вернуть желаемое значение, например:

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

вывод:

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

Как видно, добавлен нулевой байт. Чтобы избежать этого, используйте следующее:

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));

вывод:

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

Поскольку в ответе также упоминается использование паролей, возможно, стоит очистить массив, который поддерживает ByteBuffer (доступный через функцию 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));
ди-джутто
источник
Может ли конечный \ 0 быть конкретной реализацией? Я использую 1.7_51 с netbeans 7.4 и не замечаю конечного \ 0.
@orthopteroid да, этот пример может быть специфичным для jvm. Это было запущено с 64-разрядной версией Oracle 1.7.0_45 linux (из памяти). В следующей реализации ( grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… ) вы получите ошибки, если averageBytesPerChar()вернет что-либо, кроме 1 (я получаю 1.1). Из интереса, какую ОС / арку вы используете, поскольку я дважды проверил с помощью oracle 1.7.0_51 и openjdk 1.7.0_51 и обнаружил, что он сломан с 10 символами.
djsutho
@ Андрей, не беспокойся. Обратите внимание, что buffer.array()в toBytesфункции все еще нужно переопределить, в настоящее время только копия.
djsutho
@ Андрей Я отредактировал свой ответ, чтобы отразить изменения.
djsutho
@djsutho Сегодня моя платформа - windows7x64. К сожалению, не могу показать код - я использую такой код, как "System.arraycopy (str.getBytes (" UTF-8 "), 0, stor, 0, used);" в настоящее время.
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;
}
Мэтт
источник
-5

Вы можете сделать метод:

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;
}

Надеюсь это поможет

Java - это круто
источник
4
Этот ответ неверен, потому что данные char являются Unicode и, как таковые, может быть до 4 байтов на символ (возможно больше, но в реальной жизни я нашел только до 4). Простое получение одного байта из каждого символа будет работать только для очень ограниченного набора символов. Прочтите «Абсолютный минимум, который должен знать каждый разработчик программного обеспечения о Unicode и наборах символов (без оправданий!)» На сайте joelonsoftware.com/articles/Unicode.html .
Ilane