Преобразование массива char в байтовый массив и обратно - PullRequest
36 голосов
/ 08 февраля 2011

Я хочу преобразовать массив символов Java в байтовый массив без создания промежуточного значения String, поскольку массив символов содержит пароль. Я посмотрел пару методов, но все они, похоже, не срабатывают:

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, но выглядят одинаково? Я пропускаю нулевой терминатор или что-то? Что я могу сделать, чтобы преобразование и преобразование работали?

Ответы [ 8 ]

14 голосов
/ 01 мая 2013

Преобразование между символом и байтом - это кодировка и декодирование набора символов. Я предпочитаю сделать это как можно более понятным в коде.На самом деле это не означает дополнительный объем кода:

 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 (которые используют byte [] и 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);
12 голосов
/ 08 февраля 2011

Проблема в том, что вы используете конструктор String(byte[]), который использует кодировку платформы по умолчанию.Это почти никогда , что вы должны делать - если вы передадите кодировку символов UTF-16, ваши тесты, вероятно, пройдут.В настоящее время я подозреваю, что passwordBytes1AsString и passwordBytes2AsString каждый имеют длину 16 символов, причем каждый другой символ равен U + 0000.

6 голосов
/ 16 мая 2017

Оригинальный ответ

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

Отредактировано для использования StandardCharsets

public byte[] charsToBytes(char[] chars)
{
    final ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(chars));
    return Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
}

public char[] bytesToChars(byte[] bytes)
{
    final CharBuffer charBuffer = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes));
    return Arrays.copyOf(charBuffer.array(), charBuffer.limit());    
}

Вот страница JavaDoc для StandardCharsets .Обратите внимание на это на странице JavaDoc:

Эти кодировки гарантированно будут доступны в каждой реализации платформы Java.

4 голосов
/ 08 февраля 2011

Я бы использовал цикл для преобразования в байты, а другой - для преобразования в символьную.

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);
4 голосов
/ 08 февраля 2011

Если вы хотите использовать ByteBuffer и CharBuffer, не делайте простой .asCharBuffer(), который просто выполняет UTF-16 (LE или BE, в зависимости от вашей системы - вы можете установить порядок байтов с помощью order метод) преобразование (поскольку строки Java и, следовательно, ваш char[] внутренне использует эту кодировку).

Используйте Charset.forName(charsetName), а затем метод encode или decode или newEncoder /newDecoder.

При преобразовании вашего байта [] в строку вы также должны указать кодировку (и она должна быть такой же).

2 голосов
/ 12 марта 2014

Это продолжение ответа Питера Лори. Чтобы обратное преобразование (байты в символы) работало корректно для всего диапазона символов, код должен быть следующим:

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

Нам нужно "отписать" байты перед использованием (& 0xff). В противном случае половина всех возможных значений символа не будет возвращена правильно. Например, будут затронуты символы в диапазоне [0x80..0xff].

2 голосов
/ 20 июля 2012

Вы должны использовать getBytes() вместо toCharArray()

Заменить строку

char[] password = "password".toCharArray();

с

byte[] password = "password".getBytes();
1 голос
/ 15 января 2016

Когда вы используете GetBytes из строки в Java, результат возврата будет зависеть от кодировки по умолчанию для вашего компьютера (например: StandardCharsetsUTF-8 или StandardCharsets.ISO_8859_1etc ...).

Итак, всякий раз, когда вы хотите получитьBytes из объекта String. Обязательно дайте кодировать. как:

String sample = "abc";
Byte[] a_byte = sample .getBytes(StandardCharsets.UTF_8);

Давай проверим, что случилось с кодом. В java String с именем sample хранится в Unicode. каждый символ в строке хранится по 2 байта.

sample :  value: "abc"   in Memory(Hex):  00 61 00 62 00 63
        a -> 00 61
        b -> 00 62
        c -> 00 63

Но, когда мы получаем байты из строки, мы имеем

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

Чтобы получить единственный байт строки. Мы можем просто прочитать память строки и получить каждый байт строки. Ниже приведен пример кода:

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's the same reading 
//sequece of  UTF-16BE.
String sample_back= new String(result , StandardCharsets.UTF_16BE);
...