Перетворення char [] в байт []


84

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

Відповіді:


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

або

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

щоб отримати кодировку, яка не є типовою.

Оновлення: З Java 7:new String(ch).getBytes(StandardCharsets.UTF_8);


4
Використання набору символів за замовчуванням для платформи більшу частину часу є неправильним (веб-програми).
maaartinus

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 [] перед рядком? )

Пам’ятайте, що не слід записувати конфіденційні дані до журналів і переконайтесь, що JVM не буде містити посилань на них.


Наведений вище код правильний, але неефективний. Якщо вам не потрібна продуктивність, але вам потрібна безпека, ви можете її використовувати. Якщо безпека також не є метою, тоді робіть просто String.getBytes. Наведений вище код не ефективний, якщо ви дивитесь на імплементацію encodeJDK. Крім того, вам потрібно скопіювати масиви та створити буфери. Інший спосіб перетворення - це вбудований весь код позаду 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 Я вважаю, що цей метод не буде зберігати "символи" в String Pool. Таким чином ви можете працювати з даними паролів більш захищеними.
Андрій Немченко

1
@Cassian Ваш метод працює неправильно. Детальніше читайте тут stackoverflow.com/a/20604909/355491
Андрій Немченко

1
@Prabs Ні, один символ UTF-8 займає від 1 до 4 байт. Навіть один символ ASCII займає 8 бітів.
Андрій Немченко

1
Цей метод 'toBytes ()' має важливий побічний ефект. Він стирає вхідні символи. charBuffer.array () насправді є вхідними символами. Arrays.fill () насправді знищить вхід. У багатьох випадках це нормально, але іноді це створює небажаний ефект.
Гуанлян

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-розрядною версією Linux oracle 1.7.0_45 (з пам'яті). З наступною реалізацією ( 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

@Andrey не турбуйся. Зверніть увагу , що buffer.array()в toBytesфункції по- , як і раніше повинна бути перевизначений, в даний час тільки копія.
djsutho

@Andrey Я відредагував свою відповідь, щоб відобразити зміни.
djsutho

@djsutho Сьогодні моєю платформою є windows7x64. На жаль, не можу показати код - я використовую такий код, як "System.arraycopy (str.getBytes (" UTF-8 "), 0, stor, 0, використовується);" зараз.

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

Сподіваюся, це допомагає


4
Ця відповідь неправильна, оскільки дані char - це Unicode, і, таким чином, може містити до 4 байт на символ (можливо більше, але в реальному житті я знайшов лише до 4). Просто взяти по одному байту з кожного символу буде працювати лише для дуже обмеженого набору символів. Будь ласка, прочитайте "Абсолютний мінімум кожного розробника програмного забезпечення абсолютно, позитивно повинен знати про Unicode та набори символів (без виправдання!)" На joelonsoftware.com/articles/Unicode.html .
Ilane
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.