在Java中,String.length()返回字符串中字符的数量,而String.getBytes().length返回使用指定编码表示字符串所需的字节数。默认情况下,编码将是系统属性file.encoding的值,也可以通过调用System.setProperty("file.encoding", "XXX")手动设置编码名称。例如,UTF-8,Cp1252。在许多情况下,String.length()将返回与String.getBytes().length相同的值,但在某些情况下则不同。
String.length()是表示字符串所需的UTF-16代码单元的数量。也就是说,它是用于表示字符串的char值的数量(因此等于toCharArray().length)。这通常与字符串中Unicode字符(代码点)的数量相同——除非使用了UTF-16代理项。由于char在Java中是2个字节,因此如果编码是UTF-16,则String.getBytes().length将是String.length()的2倍。
String.getBytes().length是使用平台的默认编码表示字符串所需的字节数。例如,如果默认编码是UTF-16,它将恰好是String.length()返回的值的2倍。对于UTF-8,这两个长度可能不同。
如果字符串只包含ASCII代码,则这两个长度之间的关系很简单,因为它们将返回相同的值。但对于非ASCII字符,关系就比较复杂了。在ASCII字符串之外,String.getBytes().length可能更长,因为它计算表示字符串所需的字节数,而length()计算2字节的代码单元。
接下来,我们将以UTF-8编码为例来说明这种关系。
try{
System.out.println("file.encoding = "+System.getProperty("file.encoding"));
char c = 65504;
System.out.println("c = "+c);
String s = new String(new char[]{c});
System.out.println("s = "+s);
System.out.println("s.length = "+s.length());
byte[] bytes = s.getBytes("UTF-8");
System.out.println("bytes = "+Arrays.toString(bytes));
System.out.println("bytes.length = "+bytes.length);
} catch (Exception ex){
ex.printStackTrace();
}
让我们先看看输出是什么:
file.encoding = UTF-8 c = ï¿ s = ï¿ s.length = 1 bytes = [-17, -65, -96] bytes.length = 3
从输出中,我们可以看到String.getBytes()返回三个字节。这是怎么回事?由于c是65504,十六进制是0xFFE0(1111 1111 1110 0000)。根据UTF-8定义,我们可以看到:
| 位 | 第一 | 最后 | 字节 | 字节1 | 字节2 | 字节3 | 字节4 | 字节5 | 字节6 |
|---|---|---|---|---|---|---|---|---|---|
| 7 | U+0000 | U+007F | 1 | 0xxxxxxx |
|||||
| 11 | U+0080 | U+07FF | 2 | 110xxxxx |
10xxxxxx |
||||
| 16 | U+0800 | U+FFFF | 3 | 1110xxxx |
10xxxxxx |
10xxxxxx |
|||
| 21 | U+10000 | U+1FFFFF | 4 | 11110xxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
||
| 26 | U+200000 | U+3FFFFFF | 5 | 111110xx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
|
| 31 | U+4000000 | U+7FFFFFFF | 6 | 1111110x |
10xxxxxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
注意:原始规范涵盖了高达31位的数字(通用字符集的原始限制)。2003年11月,RFC 3629将UTF-8限制在U+10FFFF以内,以匹配UTF-16字符编码的约束。这去除了所有5字节和6字节序列,以及大约一半的4字节序列。
它需要三个字节才能在UTF-8中存储这个值65504。这三个字节是:
11101111 10111111 10100000
这三个字节的二进制补码整数值是:
-17 -65 -96
这就是我们得到上述输出的原因。
接下来让我们看看这个转换的JDK实现。它在Java 8中的sun.nio.cs.UTF8.java类中。
public int encode(char[] sa, int sp, int len, byte[] da) {
int sl = sp + len;
int dp = 0;
int dlASCII = dp + Math.min(len, da.length);
// ASCII only optimized loop
while (dp < dlASCII && sa[sp] < '\u0080')
da[dp++] = (byte) sa[sp++];
while (sp < sl) {
char c = sa[sp++];
if (c < 0x80) {
// Have at most seven bits
da[dp++] = (byte)c;
} else if (c < 0x800) {
// 2 bytes, 11 bits
da[dp++] = (byte)(0xc0 | (c >> 6));
da[dp++] = (byte)(0x80 | (c & 0x3f));
} else if (Character.isSurrogate(c)) {
if (sgp == null)
sgp = new Surrogate.Parser();
int uc = sgp.parse(c, sa, sp - 1, sl);
if (uc < 0) {
if (malformedInputAction() != CodingErrorAction.REPLACE)
return -1;
da[dp++] = repl;
} else {
da[dp++] = (byte)(0xf0 | ((uc >> 18)));
da[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f));
da[dp++] = (byte)(0x80 | ((uc >> 6) & 0x3f));
da[dp++] = (byte)(0x80 | (uc & 0x3f));
sp++; // 2 chars
}
} else {
// 3 bytes, 16 bits
da[dp++] = (byte)(0xe0 | ((c >> 12)));
da[dp++] = (byte)(0x80 | ((c >> 6) & 0x3f));
da[dp++] = (byte)(0x80 | (c & 0x3f));
}
}
return dp;
}
在Java 8之前,代码位于sun.io.CharToByteUTF8.java中。
public int convert(char[] input, int inOff, int inEnd,
byte[] output, int outOff, int outEnd)
throws ConversionBufferFullException, MalformedInputException
{
char inputChar;
byte[] outputByte = new byte[6];
int inputSize;
int outputSize;
charOff = inOff;
byteOff = outOff;
if (highHalfZoneCode != 0) {
inputChar = highHalfZoneCode;
highHalfZoneCode = 0;
if (input[inOff] >= 0xdc00 && input[inOff] <= 0xdfff) {
// This is legal UTF16 sequence.
int ucs4 = (highHalfZoneCode - 0xd800) * 0x400
+ (input[inOff] - 0xdc00) + 0x10000;
output[0] = (byte)(0xf0 | ((ucs4 >> 18)) & 0x07);
output[1] = (byte)(0x80 | ((ucs4 >> 12) & 0x3f));
output[2] = (byte)(0x80 | ((ucs4 >> 6) & 0x3f));
output[3] = (byte)(0x80 | (ucs4 & 0x3f));
charOff++;
highHalfZoneCode = 0;
} else {
// This is illegal UTF16 sequence.
badInputLength = 0;
throw new MalformedInputException();
}
}
while(charOff < inEnd) {
inputChar = input[charOff];
if (inputChar < 0x80) {
outputByte[0] = (byte)inputChar;
inputSize = 1;
outputSize = 1;
} else if (inputChar < 0x800) {
outputByte[0] = (byte)(0xc0 | ((inputChar >> 6) & 0x1f));
outputByte[1] = (byte)(0x80 | (inputChar & 0x3f));
inputSize = 1;
outputSize = 2;
} else if (inputChar >= 0xd800 && inputChar <= 0xdbff) {
// this is in UTF-16
if (charOff + 1 >= inEnd) {
highHalfZoneCode = inputChar;
break;
}
// check next char is valid
char lowChar = input[charOff + 1];
if (lowChar < 0xdc00 || lowChar > 0xdfff) {
badInputLength = 1;
throw new MalformedInputException();
}
int ucs4 = (inputChar - 0xd800) * 0x400 + (lowChar - 0xdc00)
+ 0x10000;
outputByte[0] = (byte)(0xf0 | ((ucs4 >> 18)) & 0x07);
outputByte[1] = (byte)(0x80 | ((ucs4 >> 12) & 0x3f));
outputByte[2] = (byte)(0x80 | ((ucs4 >> 6) & 0x3f));
outputByte[3] = (byte)(0x80 | (ucs4 & 0x3f));
outputSize = 4;
inputSize = 2;
} else {
outputByte[0] = (byte)(0xe0 | ((inputChar >> 12)) & 0x0f);
outputByte[1] = (byte)(0x80 | ((inputChar >> 6) & 0x3f));
outputByte[2] = (byte)(0x80 | (inputChar & 0x3f));
inputSize = 1;
outputSize = 3;
}
if (byteOff + outputSize > outEnd) {
throw new ConversionBufferFullException();
}
for (int i = 0; i < outputSize; i++) {
output[byteOff++] = outputByte[i];
}
charOff += inputSize;
}
return byteOff - outOff;
}
这段代码正在执行上表中描述的操作。
对于其他字符集,您可以按照相同的方式找出调用String.getBytes()时字节是什么。
这篇文章的一个要点是,在尝试调用String.getBytes().length并期望它与String.length()相同的时候要小心,尤其是在应用程序中存在低级字节操作的情况下,例如数据加密和解密。
No comment for this article.