Преобразование массива char в массив байтов и обратно
Я хочу преобразовать массив Java char в массив байтов без создания промежуточного String
, так как массив char содержит пароль. Я просмотрел несколько методов, но все они, похоже, терпят неудачу:
char[] password = "password".toCharArray();
byte[] passwordBytes1 = new byte[password.length*2];
ByteBuffer.wrap(passwordBytes1).asCharBuffer().put(password);
byte[] passwordBytes2 = new byte[password.length*2];
for(int i=0; i<password.length; i++) {
passwordBytes2[2*i] = (byte) ((password[i]&0xFF00)>>8);
passwordBytes2[2*i+1] = (byte) (password[i]&0x00FF);
}
String passwordAsString = new String(password);
String passwordBytes1AsString = new String(passwordBytes1);
String passwordBytes2AsString = new String(passwordBytes2);
System.out.println(passwordAsString);
System.out.println(passwordBytes1AsString);
System.out.println(passwordBytes2AsString);
assertTrue(passwordAsString.equals(passwordBytes1) || passwordAsString.equals(passwordBytes2));
Утверждение всегда терпит неудачу (и, критически, когда код используется в процессе производства, пароль отклоняется), но заявления печати распечатывают пароль три раза. Почему passwordBytes1AsString
и passwordBytes2AsString
отличаются от passwordAsString
, но кажутся идентичными? Я пропускаю нулевой терминатор или что-то еще? Что я могу сделать, чтобы сделать преобразование и отмену работы?
Ответы
Ответ 1
Проблема заключается в использовании конструктора String(byte[])
, который использует стандартную кодировку платформы. Это почти никогда не то, что вы должны делать - если вы передадите "UTF-16" в качестве кодировки символов для работы, ваши тесты, вероятно, пройдут. В настоящее время я подозреваю, что passwordBytes1AsString
и passwordBytes2AsString
имеют длину 16 символов, причем каждый другой символ U + 0000.
Ответ 2
Преобразование между char и байтом - кодировка и декодирование набора символов. Я предпочитаю сделать это как можно более ясным в коде. Это не означает дополнительный объем кода:
Charset latin1Charset = Charset.forName("ISO-8859-1");
charBuffer = latin1Charset.decode(ByteBuffer.wrap(byteArray)); // also decode to String
byteBuffer = latin1Charset.encode(charBuffer); // also decode from String
В сторону:
Классы java.nio и классы java.io Reader/Writer используют ByteBuffer и CharBuffer (которые используют байты [] и char [] в качестве массивов поддержки). Поэтому часто предпочтительнее использовать эти классы напрямую. Однако вы всегда можете:
byteArray = ByteBuffer.array(); byteBuffer = ByteBuffer.wrap(byteArray);
byteBuffer.get(byteArray); charBuffer.put(charArray);
charArray = CharBuffer.array(); charBuffer = ByteBuffer.wrap(charArray);
charBuffer.get(charArray); charBuffer.put(charArray);
Ответ 3
Если вы хотите использовать ByteBuffer и CharBuffer, не делайте простого .asCharBuffer()
, который просто делает UTF-16 (LE или BE, в зависимости от вашей системы), вы можете установить порядок байтов с помощью order
) (поскольку Java-строки и, следовательно, ваш char[]
внутренне использует эту кодировку).
Используйте Charset.forName(charsetName)
, а затем его метод encode
или decode
или newEncoder
/newDecoder
.
При преобразовании вашего байта [] в String вы также должны указать кодировку (и она должна быть одной и той же).
Ответ 4
Я бы сделал это, используя цикл для преобразования в байты, а другой - для возврата к char.
char[] chars = "password".toCharArray();
byte[] bytes = new byte[chars.length*2];
for(int i=0;i<chars.length;i++) {
bytes[i*2] = (byte) (chars[i] >> 8);
bytes[i*2+1] = (byte) chars[i];
}
char[] chars2 = new char[bytes.length/2];
for(int i=0;i<chars2.length;i++)
chars2[i] = (char) ((bytes[i*2] << 8) + (bytes[i*2+1] & 0xFF));
String password = new String(chars2);
Ответ 5
Вы должны использовать getBytes()
вместо toCharArray()
Заменить строку
char[] password = "password".toCharArray();
с
byte[] password = "password".getBytes();
Ответ 6
Это продолжение ответа Питера Лоури. Для того чтобы обратное преобразование (байты к символам) корректно работало для всего диапазона символов, код должен быть следующим:
char[] chars = new char[bytes.length/2];
for (int i = 0; i < chars.length; i++) {
chars[i] = (char) (((bytes[i*2] & 0xff) << 8) + (bytes[i*2+1] & 0xff));
}
Нам нужно "unsign" байт перед использованием (& 0xff
). В противном случае половина всех возможных значений char вернется неправильно. Например, будут затронуты символы в диапазоне [0x80..0xff]
.
Ответ 7
public byte[] charsToBytes(char[] chars){
Charset charset = Charset.forName("UTF-8");
ByteBuffer byteBuffer = charset.encode(CharBuffer.wrap(chars));
return Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
}
public char[] bytesToChars(byte[] bytes){
Charset charset = Charset.forName("UTF-8");
CharBuffer charBuffer = charset.decode(ByteBuffer.wrap(bytes));
return Arrays.copyOf(charBuffer.array(), charBuffer.limit());
}
Ответ 8
Когда вы используете GetBytes From String в Java, результат возврата будет зависеть от кодировки по умолчанию вашего компьютера (например: StandardCharsetsUTF-8 или StandardCharsets.ISO_8859_1etc...).
Итак, всякий раз, когда вы хотите getBytes из объекта String. Обязательно укажите кодировку. например:
String sample = "abc";
Byte[] a_byte = sample .getBytes(StandardCharsets.UTF_8);
Проверьте, что произошло с кодом.
В java строка с именем sample хранится в Unicode. каждый char в String, хранящийся на 2 байта.
sample : value: "abc" in Memory(Hex): 00 61 00 62 00 63
a -> 00 61
b -> 00 62
c -> 00 63
Но, когда мы получаемBytes From String, мы имеем
Byte[] a_byte = sample .getBytes(StandardCharsets.UTF_8)
//result is : 61 62 63
//length: 3 bytes
Byte[] a_byte = sample .getBytes(StandardCharsets.UTF_16BE)
//result is : 00 61 00 62 00 63
//length: 6 bytes
Чтобы получить одинарный байт строки. Мы можем просто прочитать память строки и получить каждый байт String.Below - это пример кода:
public static byte[] charArray2ByteArray(char[] chars){
int length = chars.length;
byte[] result = new byte[length*2+2];
int i = 0;
for(int j = 0 ;j<chars.length;j++){
result[i++] = (byte)( (chars[j] & 0xFF00) >> 8 );
result[i++] = (byte)((chars[j] & 0x00FF)) ;
}
return result;
}
Обычаи:
String sample = "abc";
//First get the chars of the String,each char has two bytes(Java).
Char[] sample_chars = sample.toCharArray();
//Get the bytes
byte[] result = charArray2ByteArray(sample_chars).
//Back to String.
//Make sure we use UTF_16BE. Because we read the memory of Unicode of
//the String from Left to right. That the same reading
//sequece of UTF-16BE.
String sample_back= new String(result , StandardCharsets.UTF_16BE);