Chuyển đổi biểu đồ [] thành byte []


84

Tôi muốn chuyển đổi một mảng ký tự thành một mảng byte trong Java. Có những phương pháp nào để thực hiện chuyển đổi này?

Câu trả lời:


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

hoặc là

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

để có được bộ ký tự không mặc định.

Cập nhật: Kể từ Java 7:new String(ch).getBytes(StandardCharsets.UTF_8);


4
Việc sử dụng bộ ký tự mặc định của nền tảng hầu hết đều sai (ứng dụng web).
maaartinus

4
Đây là một giải pháp nhỏ, vì sử dụng một Chuỗi mới, không gian cần thiết cho hoạt động được nhân đôi. Nó sẽ không hoạt động tốt cho các đầu vào cực lớn.
Levent Divilioglu

167

Chuyển đổi mà không cần tạo Stringđối tượng:

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

Sử dụng:

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

Giải pháp được lấy cảm hứng từ đề xuất Swing để lưu trữ mật khẩu trong char []. (Xem Tại sao char [] được ưu tiên hơn Chuỗi cho mật khẩu? )

Hãy nhớ không ghi dữ liệu nhạy cảm vào nhật ký và đảm bảo rằng JVM sẽ không giữ bất kỳ tham chiếu nào đến nó.


Đoạn mã trên là đúng nhưng không hiệu quả. Nếu bạn không cần hiệu suất nhưng muốn bảo mật, bạn có thể sử dụng nó. Nếu bảo mật cũng không phải là một mục tiêu thì hãy làm đơn giản String.getBytes. Mã trên không hiệu quả nếu bạn xem thường việc triển khai encodetrong JDK. Bên cạnh đó bạn cần sao chép mảng và tạo bộ đệm. Một cách khác để chuyển đổi là nội tuyến tất cả mã phía sau encode(ví dụ cho 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"))

Xin lỗi vì đã sử dụng ngôn ngữ Scala. Nếu bạn gặp vấn đề với việc chuyển đổi mã này sang Java, tôi có thể viết lại nó. Điều gì về hiệu suất luôn luôn kiểm tra trên dữ liệu thực (với JMH chẳng hạn). Mã này trông rất giống với những gì bạn có thể thấy trong JDK [ 2 ] và Protobuf [ 3 ].


Điều này sẽ không tạo ByteBuffer? Tôi đoán nó ít tốn kém hơn một đối tượng Chuỗi?
Andi Jay

15
@CrazyJay Tôi tin rằng phương pháp này sẽ không lưu trữ "ký tự" trong Nhóm chuỗi. Bằng cách này, bạn có thể làm việc với dữ liệu mật khẩu an toàn hơn.
Andrii Nemchenko

1
@Cassian Phương pháp của bạn hoạt động không chính xác. Đọc chi tiết tại đây stackoverflow.com/a/20604909/355491
Andrii Nemchenko

1
@Prabs Không, một ký tự UTF-8 chiếm từ 1 đến 4 byte. Ngay cả một ký tự ASCII cũng chiếm 8 bit.
Andrii Nemchenko

1
Phương thức 'toBytes ()' này có một tác dụng phụ quan trọng. Nó xóa sạch các ký tự đầu vào. charBuffer.array () thực sự là các ký tự đầu vào. Arrays.fill () sẽ thực sự xóa sạch đầu vào. Trong nhiều trường hợp thì không sao, nhưng đôi khi nó tạo ra hiệu ứng không mong muốn.
Guangliang

19

Chỉnh sửa: Câu trả lời của Andrey đã được cập nhật nên phần sau không còn áp dụng nữa.

Câu trả lời của Andrey (được bình chọn cao nhất tại thời điểm viết bài) hơi không chính xác. Tôi đã có thể thêm điều này như bình luận nhưng tôi không đủ uy tín.

Trong câu trả lời của Andrey:

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

lệnh gọi tới array () có thể không trả về giá trị mong muốn, ví dụ:

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

đầu ra:

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

Như có thể thấy một byte 0 đã được thêm vào. Để tránh điều này, hãy sử dụng như sau:

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

đầu ra:

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

Vì câu trả lời cũng ám chỉ đến việc sử dụng mật khẩu, có thể đáng để xóa mảng hỗ trợ ByteBuffer (được truy cập thông qua hàm 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));

Phần cuối \ 0 có thể là cách triển khai cụ thể không? Tôi đang sử dụng 1.7_51 với netbeans 7.4 và không nhận thấy bất kỳ dấu \ 0 nào.

@orthopteroid vâng, ví dụ này có thể là jvm cụ thể. Điều này đã được chạy với oracle 1.7.0_45 linux 64 bit (từ bộ nhớ). Với cách triển khai sau ( grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… ), bạn sẽ gặp lỗi nếu averageBytesPerChar()trả về bất kỳ thứ gì khác với 1 (tôi nhận được 1.1). Không quan tâm bạn đang sử dụng hệ điều hành / vòm nào khi tôi kiểm tra kỹ với oracle 1.7.0_51 và openjdk 1.7.0_51 và thấy nó bị hỏng với 10 ký tự.
djsutho

@Andrey đừng lo lắng. Lưu ý rằng buffer.array()trong toByteshàm vẫn cần ghi đè, hiện tại chỉ có bản sao.
djsutho

@Andrey Tôi đã chỉnh sửa câu trả lời của mình để phản ánh những thay đổi.
djsutho

@djsutho Hôm nay, nền tảng của tôi là windows7x64. Rất tiếc, không thể hiển thị mã - Tôi đang sử dụng mã như "System.arraycopy (str.getBytes (" UTF-8 "), 0, stor, 0, used);" hiện nay.

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

Bạn có thể thực hiện một phương pháp:

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

Hi vọng điêu nay co ich


4
Câu trả lời này không chính xác vì dữ liệu char là Unicode và như vậy có thể có tối đa 4 byte cho mỗi ký tự (có thể nhiều hơn, nhưng trong cuộc sống thực, tôi chỉ tìm thấy tối đa 4). Chỉ cần lấy một byte từ mỗi ký tự sẽ chỉ hoạt động đối với một bộ ký tự rất hạn chế. Vui lòng đọc 'Mức tối thiểu tuyệt đối mà mọi nhà phát triển phần mềm tuyệt đối phải biết về Unicode và Bộ ký tự (Không có lý do nào!)' Tại joelonsoftware.com/articles/Unicode.html .
Ilane
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.