为什么带有 UTF-8 的新字符串包含更多字节
Why new String with UTF-8 contains more bytes
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.getBytes().length
它时 returns 一些其他值。最大值为 32。为什么一个 16 字节的数组最终会生成另一个大小的字节字符串?
但如果我这样做 string.length()
它 returns 16.
生成的字节可能包含有效的多字节字符。
以此为例。该字符串仅包含一个字符,但作为字节表示它需要三个字节。
String s = "Ω";
System.out.println("length = " + s.length());
System.out.println("bytes = " + Arrays.toString(s.getBytes("UTF-8")));
String.length()
return 字符串的字符长度。字符 Ω
是一个字符,而在 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.
这是因为您的 bytes 首先被转换为 Unicode 字符串,它试图从这些字节创建 UTF-8 char 序列。如果一个字节不能被视为 ASCII 字符,也不能与下一个字节一起捕获以形成合法的 unicode 字符,则将其替换为“�”。在调用 String#getBytes()
时,此类 char 被转换为 3 个字节,从而向结果输出添加 2 个额外字节。
如果您幸运地只生成 ASCII 字符,String#getBytes()
将 return 16 字节数组,否则,生成的数组可能会更长。例如下面的代码片段:
byte[] b = new byte[16];
Arrays.fill(b, (byte) 190);
b = new String(b, "UTF-8").getBytes();
returns 长度为 48(!) 字节的数组。
String.getBytes().length 可能更长,因为它计算表示字符串所需的字节数,而 length() 计算 2 字节代码单元.
阅读更多here
因误解byte
s和char
s之间的关系而产生的经典错误,所以我们再来一次。
byte
和char
之间没有一对一的映射;这完全取决于您使用的字符编码(在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
。
我知道这不是一个确切的答案,但你没有清楚地解释你的问题;上面的例子已经表明你对 byte
是什么和 char
是什么有错误的理解。
这将尝试创建一个字符串,假设字节是 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,一个 16 位 char
的 UTF-16 数组。
人们应该完全避免使用 String
作为 byte[]
。它总是涉及转换,占用双倍内存并且容易出错。
如果您查看生成的字符串,就会发现生成的大部分随机字节都不是有效的 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
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.getBytes().length
它时 returns 一些其他值。最大值为 32。为什么一个 16 字节的数组最终会生成另一个大小的字节字符串?
但如果我这样做 string.length()
它 returns 16.
生成的字节可能包含有效的多字节字符。
以此为例。该字符串仅包含一个字符,但作为字节表示它需要三个字节。
String s = "Ω";
System.out.println("length = " + s.length());
System.out.println("bytes = " + Arrays.toString(s.getBytes("UTF-8")));
String.length()
return 字符串的字符长度。字符 Ω
是一个字符,而在 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)
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.
这是因为您的 bytes 首先被转换为 Unicode 字符串,它试图从这些字节创建 UTF-8 char 序列。如果一个字节不能被视为 ASCII 字符,也不能与下一个字节一起捕获以形成合法的 unicode 字符,则将其替换为“�”。在调用 String#getBytes()
时,此类 char 被转换为 3 个字节,从而向结果输出添加 2 个额外字节。
如果您幸运地只生成 ASCII 字符,String#getBytes()
将 return 16 字节数组,否则,生成的数组可能会更长。例如下面的代码片段:
byte[] b = new byte[16];
Arrays.fill(b, (byte) 190);
b = new String(b, "UTF-8").getBytes();
returns 长度为 48(!) 字节的数组。
String.getBytes().length 可能更长,因为它计算表示字符串所需的字节数,而 length() 计算 2 字节代码单元.
阅读更多here
因误解byte
s和char
s之间的关系而产生的经典错误,所以我们再来一次。
byte
和char
之间没有一对一的映射;这完全取决于您使用的字符编码(在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
。
我知道这不是一个确切的答案,但你没有清楚地解释你的问题;上面的例子已经表明你对 byte
是什么和 char
是什么有错误的理解。
这将尝试创建一个字符串,假设字节是 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,一个 16 位 char
的 UTF-16 数组。
人们应该完全避免使用 String
作为 byte[]
。它总是涉及转换,占用双倍内存并且容易出错。
如果您查看生成的字符串,就会发现生成的大部分随机字节都不是有效的 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