Konwersja znaku [] na bajt []

84

Chciałbym przekonwertować tablicę znaków na tablicę bajtów w Javie. Jakie są metody dokonywania tej konwersji?

Arun Abraham
źródło

Odpowiedzi:

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

lub

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

aby uzyskać inny niż domyślny zestaw znaków.

Aktualizacja: od wersji Java 7:new String(ch).getBytes(StandardCharsets.UTF_8);

Tarlog
źródło
4
Używanie domyślnego zestawu znaków platformy jest przez większość czasu niewłaściwe (aplikacje internetowe).
maaartinus
4
Jest to trywialne rozwiązanie, ponieważ użycie nowego ciągu powoduje podwojenie miejsca potrzebnego na operację. Nie będzie działać zbyt dobrze przy bardzo dużych nakładach.
Levent Divilioglu
167

Konwertuj bez tworzenia Stringobiektu:

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

Stosowanie:

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

Rozwiązanie zostało zainspirowane zaleceniem Swing dotyczącym przechowywania haseł w char []. (Zobacz Dlaczego hasła char [] są preferowane zamiast ciągów znaków? )

Pamiętaj, aby nie zapisywać wrażliwych danych w dziennikach i upewnij się, że JVM nie będzie zawierał żadnych odniesień do nich.


Powyższy kod jest poprawny, ale nieskuteczny. Jeśli nie potrzebujesz wydajności, ale chcesz bezpieczeństwa, możesz go użyć. Jeśli bezpieczeństwo również nie jest celem, zrób to po prostu String.getBytes. Powyższy kod nie jest skuteczny, jeśli spojrzysz na implementację encodew JDK. Poza tym musisz kopiować tablice i tworzyć bufory. Innym sposobem konwersji jest wbudowany cały kod za encode(na przykład dla 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"))

Przepraszam za używanie języka Scala. Jeśli masz problemy z konwersją tego kodu do Java mogę go przepisać. A co z wydajnością, zawsze sprawdzaj rzeczywiste dane (na przykład z JMH). Ten kod wygląda bardzo podobnie do tego, co można zobaczyć w JDK [ 2 ] i Protobuf [ 3 ].

Andrii Nemchenko
źródło
Czy to nie stworzy ByteBuffer? Wydaje mi się, że to mniej kosztowne niż obiekt typu String?
Andi Jay
15
@CrazyJay Uważam, że ta metoda nie przechowuje „znaków” w puli ciągów. W ten sposób możesz bezpieczniej pracować z hasłami.
Andrii Nemchenko
1
@Cassian Twoja metoda działa nieprawidłowo. Przeczytaj szczegóły tutaj stackoverflow.com/a/20604909/355491
Andrii Nemchenko
1
@Prabs Nie, jeden znak UTF-8 zajmuje od 1 do 4 bajtów. Nawet jeden znak ASCII zajmuje 8 bitów.
Andrii Nemchenko
1
Ta metoda „toBytes ()” ma istotny efekt uboczny. Czyści znaki wejściowe. charBuffer.array () w rzeczywistości jest znakami wejściowymi. Arrays.fill () faktycznie wyczyściłoby dane wejściowe. W wielu przypadkach jest to w porządku, ale czasami powoduje niepożądany efekt.
Guangliang
19

Edycja: odpowiedź Andreya została zaktualizowana, więc poniższe nie mają już zastosowania.

Odpowiedź Andreya (najwięcej głosów w momencie pisania) jest nieco niepoprawna. Dodałbym to jako komentarz, ale nie mam wystarczającej reputacji.

W odpowiedzi Andreya:

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

wywołanie funkcji array () może nie zwrócić żądanej wartości, na przykład:

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

wynik:

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

Jak widać, dodano bajt zerowy. Aby tego uniknąć, użyj następujących:

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

wynik:

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

Ponieważ odpowiedź dotyczyła również używania haseł, warto wyczyścić tablicę, która obsługuje ByteBuffer (dostęp do niej uzyskuje się za pośrednictwem funkcji 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
źródło
Czy końcowe \ 0 może być specyficzne dla implementacji? Używam 1.7_51 z netbeans 7.4 i nie zauważam żadnych końcowych \ 0.
@orthopteroid tak, ten przykład może być specyficzny dla jvm. Zostało to uruchomione z 64-bitowym systemem Linux Oracle 1.7.0_45 (z pamięci). Z następującą implementacją ( grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… ) otrzymasz błędy, jeśli averageBytesPerChar()zwróci cokolwiek innego niż 1 (otrzymam 1.1). Nie interesuje mnie, jakiego systemu operacyjnego / arch używasz, gdy dwukrotnie sprawdziłem z oracle 1.7.0_51 i openjdk 1.7.0_51 i stwierdziłem, że jest uszkodzony z 10 znakami.
djsutho
@Andrey nie ma obaw. Zauważ, że buffer.array()w toBytesfunkcji nadal musi zostać zastąpiona, obecnie jest tylko kopia.
djsutho
@Andrey Zmieniłem odpowiedź, aby odzwierciedlić zmiany.
djsutho
@djsutho Dziś moja platforma to windows7x64. Przepraszamy, nie mogę pokazać kodu - używam kodu takiego jak „System.arraycopy (str.getBytes („ UTF-8 ”), 0, stor, 0, używany);” teraz.
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;
}
Matt
źródło
-5

Możesz stworzyć metodę:

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

Mam nadzieję że to pomoże

Java jest fajna
źródło
4
Ta odpowiedź jest nieprawidłowa, ponieważ dane znaków są w formacie Unicode i jako takie mogą występować do 4 bajtów na znak (więcej jest możliwych, ale w rzeczywistości znalazłem tylko 4). Po prostu pobranie jednego bajtu z każdego znaku będzie działać tylko w przypadku bardzo ograniczonego zestawu znaków. Przeczytaj „Absolutne minimum, które każdy programista absolutnie, pozytywnie musi wiedzieć o Unicode i zestawach znaków (bez wymówek!)” Na joelonsoftware.com/articles/Unicode.html .
Ilane