为什么带有UTF-8的新String包含更多字节

时间:2015-04-20 14:04:51

标签: java string byte

byte bytes[] = new byte[16];
random.nextBytes(bytes);
try {
   return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
   log.warn("Hash generation failed", e);
}

当我使用给定方法生成String时,当我应用string.getBytes().length时,它返回一些其他值。 Max为32.为什么16字节数组最终生成另一个大小的字节字符串?

但如果我做string.length()则返回16。

6 个答案:

答案 0 :(得分:5)

这是因为您的 bytes 首先转换为Unicode字符串,该字符串尝试从这些字节创建UTF-8 char 序列。如果一个字节不能被视为ASCII字符,也不能被下一个字节捕获以形成合法的unicode字符,则它将替换为“ ”。调用String#getBytes()时,此类字符将转换为3个字节,从而为结果输出添加2个额外字节。

如果你很幸运只生成ASCII字符,String#getBytes()将返回16字节数组,否则,结果数组可能会更长。例如,以下代码段:

byte[] b = new byte[16]; 
Arrays.fill(b, (byte) 190);  
b = new String(b, "UTF-8").getBytes(); 

返回48(!)字节长的数组。

答案 1 :(得分:3)

生成的字节可能包含有效的多字节字符。

以此为例。该字符串只包含一个字符,但作为字节表示,它需要三个字节。

String s = "Ω";
System.out.println("length = " + s.length());
System.out.println("bytes = " + Arrays.toString(s.getBytes("UTF-8")));

String.length()以字符形式返回字符串的长度。字符是一个字符,而它是UTF-8中的3字节长。

如果你改变你的代码

Random random = new Random();
byte bytes[] = new byte[16];
random.nextBytes(bytes);
System.out.println("string = " + new String(bytes, "UTF-8").length());
System.out.println("string = " + new String(bytes, "ISO-8859-1").length());

使用不同的字符集解释相同的字节。并遵循String(byte[] b, String charset)

的javadoc
The length of the new String is a function of the charset, and hence may
not be equal to the length of the byte array.

答案 2 :(得分:3)

由于对bytechar s之间关系的误解而产生的经典错误,所以我们再来一次。

bytechar之间没有1对1的映射关系;这一切都取决于你使用的字符编码(在Java中,这是一个Charset)。

更糟糕的是:给定byte序列,或不可以编码为char序列。

试试这个例子:

final byte[] buf = new byte[16];
new Random().nextBytes(buf);

final Charset utf8 = StandardCharsets.UTF_8;
final CharsetDecoder decoder = utf8.newDecoder()
    .onMalformedInput(CodingErrorAction.REPORT);

decoder.decode(ByteBuffer.wrap(buf));

这很可能抛出MalformedInputException

我知道这不是答案,但是你没有清楚地解释你的问题;并且上面的示例已经表明您对bytechar的内容之间存在错误的理解。

答案 3 :(得分:1)

如果查看您正在生成的字符串,您生成的大多数随机字节都不会形成有效的UTF-8字符。因此,String构造函数将其替换为unicode' REPLACEMENT CHARACTER' �,占用3个字节,0xFFFD。

举个例子:

public static void main(String[] args) throws UnsupportedEncodingException
{
    Random random = new Random();

    byte bytes[] = new byte[16];
    random.nextBytes(bytes);
    printBytes(bytes);

    final String s = new String(bytes, "UTF-8");
    System.out.println(s);
    printCharacters(s);
}

private static void printBytes(byte[] bytes)
{
    for (byte aByte : bytes)
    {
        System.out.print(
                Integer.toHexString(Byte.toUnsignedInt(aByte)) + " ");
    }
    System.out.println();
}

private static void printCharacters(String s)
{
    s.codePoints().forEach(i -> System.out.println(Character.getName(i)));
}

在给定的运行中,我得到了这个输出:

30 41 9b ff 32 f5 38 ec ef 16 23 4a 54 26 cd 8c 
0A��2�8��#JT&͌
DIGIT ZERO
LATIN CAPITAL LETTER A
REPLACEMENT CHARACTER
REPLACEMENT CHARACTER
DIGIT TWO
REPLACEMENT CHARACTER
DIGIT EIGHT
REPLACEMENT CHARACTER
REPLACEMENT CHARACTER
SYNCHRONOUS IDLE
NUMBER SIGN
LATIN CAPITAL LETTER J
LATIN CAPITAL LETTER T
AMPERSAND
COMBINING ALMOST EQUAL TO ABOVE

答案 4 :(得分:0)

String.getBytes()。length 可能更长,因为它计算表示字符串所需的字节数,而length()计算2字节代码单位。

了解更多here

答案 5 :(得分:0)

这将尝试创建一个字符串,假设字节是UTF-8。

new String(bytes, "UTF-8");

这通常会出现严重错误,因为UTF-8多字节序列可能无效。

像:

String s = new String(new byte[] { -128 }, StandardCharsets.UTF_8);

第二步:

byte[] bytes = s.getBytes();

将使用平台编码(System.getProperty("file.encoding"))。更好地指定它。

byte[] bytes = s.getBytes(StandardCharsets.UTF_8);

应该意识到,内部String将维护Unicode,即UTF-16中的16位char数组。

应该完全放弃对String使用byte[]。它总是涉及转换,成本双倍内存并且容易出错。