将char[]转换为byte[]

100

我想在Java中将字符数组转换为字节数组。 有哪些方法可以进行此转换?

6个回答

186

不创建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[] 中。(参见 为什么 char[] 被优先于 String 用于密码存储?

记住不要将敏感数据写入日志,并确保 JVM 不会持有任何对其的引用。

此方法仅用于安全方面的考虑。如果数据不敏感,最好使用 String.getBytes


以下是伪代码(实际上是Scala代码),用于手动执行相同的操作,针对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"))

这段代码看起来很像JDK[2]和Protobuf[3]中的内容。

这会不会创建一个 ByteBuffer?我想这比一个 String 对象更节省成本? - Andi Jay
@Andrii Nemchenko,如果您使用UTF-8(最初我使用的是US-ASCII),则在最后一个位置会得到一个尾随0。我已经重构了代码,现在它可以正确地使用UTF-8。感谢您的注意! - Cassian
@AndriiNemchenko 这里一个字符占用1个字节。我能把它变成半个字节吗?我记得读过一个字符占用4位的文章。 - Prabs
1
这个“toBytes()”方法有一个重要的副作用。它会擦除输入字符。charBuffer.array()实际上就是输入字符。Arrays.fill()实际上会清除输入。在许多情况下,这是可以接受的,但有时会产生不良影响。 - Guangliang

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

或者,要获取非默认字符集:

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

更新:自Java 7起:

new String(ch).getBytes(StandardCharsets.UTF_8);

5
在大多数情况下(网络应用程序),使用平台的默认字符集是错误的。 - maaartinus
5
这是一个简单的解决方案,因为它使用了一个新的字符串,所以操作所需的空间增加了一倍。对于非常大的输入,它将无法很好地工作。 - Levent Divilioglu
1
请注意,如果安全性是一个问题,这种方法并不理想,因为Java会缓存字符串。 - NBJack
如果使用char数组来避免字符串,那么这是不安全的。(参考Java中的String vs char[] for password) - frhack

21

编辑:Andrey的答案已经更新,因此以下内容不再适用。

Andrey的答案(在撰写本文时获得最高投票)略有误。虽然我想以评论的形式添加,但我声望不够。

在Andrey的答案中:

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可能是实现特定的吗?我正在使用带有Netbeans 7.4的1.7_51,并没有注意到任何结尾的\0。 - user968363
@orthopteroid 是的,这个例子可能是针对JVM特定的。我是在oracle 1.7.0_45 linux 64位上运行的(从记忆中得知)。使用以下实现(http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/nio/charset/CharsetEncoder.java?av=f#773),如果`averageBytesPerChar()`返回除1以外的任何值(我得到了1.1),则会出现错误。顺便问一下,您使用的是什么操作系统/架构?我使用oracle 1.7.0_51和openjdk 1.7.0_51进行了双重检查,并发现它在10个字符时出现故障。 - djsutho
@Andrey 不用担心。请注意,在 toBytes 函数中,buffer.array() 仍需要被覆盖,目前只有复制。 - djsutho
@Andrey,我已经编辑了我的答案以反映这些更改。 - djsutho
@djsutho 今天我的平台是Windows7x64。抱歉,无法展示代码 - 我现在正在使用类似于“System.arraycopy(str.getBytes("UTF-8"), 0, stor, 0, used);”的代码。 - user968363
我猜这也假设arrayOffset()为0。我在想是否可以这样做...我们自己的代码也是这样做的,但我想寻找一个更清晰的替代方案。 - Hakanai

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

0
如果您只想转换数据容器(数组)类型本身,仅涉及数据大小并对任何编码不加区分:
// original byte[]
byte[] pattern = null;
char[] arr = new char[pattern.length * 2];
ByteBuffer wrapper = ByteBuffer.wrap(pattern);
wrapper.position(0);
int i = 0;
while(wrapper.hasRemaining()) {
    char character = wrapper.remaining() < 2 ? ((char) (((int) wrapper.get()) << 8)) : wrapper.getChar();
    arr[i++] = character;
}

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

希望这能有所帮助


8
这个答案是不正确的,因为字符数据是Unicode编码的,每个字符可能会有多达4个字节(尽管在现实生活中,我只找到了最多4个字节的情况)。仅仅从每个字符中取出一个字节只适用于非常有限的字符集。请阅读http://www.joelonsoftware.com/articles/Unicode.html上的“每个软件开发人员绝对、肯定必须知道的Unicode和字符集的绝对最小值(没有任何借口!)”。 - Ilane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接