JAVA中⼀个汉字占多少个字符(转载)
1、先说重点:
不同的编码格式占字节数是不同的,UTF-8编码下⼀个中⽂所占字节也是不确定的,可能是2个、3个、4个字节;
2、以下是源码:
1 @Test
2 public void test1() throws UnsupportedEncodingException {
3 String a = "名";
4 System.out.println("UTF-8编码长度:"+a.getBytes("UTF-8").length);
5 System.out.println("GBK编码长度:"+a.getBytes("GBK").length);
6 System.out.println("GB2312编码长度:"+a.getBytes("GB2312").length);
7 System.out.println("==========================================");
8
9 String c = "0x20001";
10 System.out.println("UTF-8编码长度:"+c.getBytes("UTF-8").length);
11 System.out.println("GBK编码长度:"+c.getBytes("GBK").length);
12 System.out.println("GB2312编码长度:"+c.getBytes("GB2312").length);
13 System.out.println("==========================================");
14
15 char[] arr = Chars(0x20001);
16 String s = new String(arr);
17 System.out.println("char array length:" + arr.length);
18 System.out.println("content:| " + s + " |");
19 System.out.println("String length:" + s.length());
20 System.out.println("UTF-8编码长度:"+s.getBytes("UTF-8").length);
21 System.out.println("GBK编码长度:"+s.getBytes("GBK").length);
22 System.out.println("GB2312编码长度:"+s.getBytes("GB2312").length);
23 System.out.println("==========================================");
24 }
3、运⾏结果
1 UTF-8编码长度:3
2 GBK编码长度:2
3 GB2312编码长度:2
4 ==========================================
5 UTF-8编码长度:4
6 GBK编码长度:1
7 GB2312编码长度:1
8 ==========================================
9 char array length:2
10 content:| |
11 String length:2
12 UTF-8编码长度:4
13 GBK编码长度:1
14 GB2312编码长度:1
15 ==========================================
4、⼏种编码格式的简单介绍
⼏种编码格式。
ASCII 码
学过计算机的⼈都知道 ASCII 码,总共有 128 个,⽤⼀个字节的低 7 位表⽰,0~31 是控制字符如换⾏回车删除等;32~126 是打印字符,可以通过键盘输⼊并且能够显⽰出来。
ISO-8859-1
128 个字符显然是不够⽤的,于是 ISO 组织在 ASCII 码基础上⼜制定了⼀些列标准⽤来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了⼤多数西欧语⾔字符,所有应⽤的最⼴泛。ISO-8859-1 仍然是单字节编码,它总共能表⽰ 256 个字符。
GB2312
它的全称是《信息交换⽤汉字编码字符集基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。
GBK
全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加⼊更多的汉字,它的编码范围是 8140~FEFE(去掉
XX7F)总共有 23940 个码位,它能表⽰ 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说⽤ GB2312 编码的汉字可以⽤ GBK 来解码,并且不会有乱码。
GB18030
全称是《信息交换⽤汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与 GB2312 编码兼容,这个虽然是国家标准,但是实际应⽤系统中使⽤的并不⼴泛。
UTF-16
说到 UTF 必须要提到 Unicode(Universal Code 统⼀码),ISO 试图想创建⼀个全新的超语⾔字典,世界上所有的语⾔都可以通过这本字典来相互翻译。可想⽽知这个字典是多么的复杂,关于 Unicode 的详细规范可以参考相应⽂档。Unicode 是 Java 和 XML 的基础,下⾯详细介绍 Unicode 在计算机中的存储形式。
UTF-16 具体定义了 Unicode 字符在计算机中存取⽅法。UTF-16 ⽤两个字节来表⽰ Unicode 转化格式,这个是定长的表⽰⽅法,不论什么字符都可以⽤两个字节表⽰,两个字节是 16 个bit,所以叫 UTF-
16。UTF-16 表⽰字符⾮常⽅便,每两个字节表⽰⼀个字符,这个在字符串操作时就⼤⼤简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的⼀个很重要的原因。
UTF-8
UTF-16 统⼀采⽤两个字节表⽰⼀个字符,虽然在表⽰上⾮常简单⽅便,但是也有其缺点,有很⼤⼀部分字符⽤⼀个字节就可以表⽰的现在要两个字节表⽰,存储空间放⼤了⼀倍,在现在
的⽹络带宽还⾮常有限的今天,这样会增⼤⽹络传输的流量,⽽且也没必要。⽽ UTF-8 采⽤了⼀种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。
UTF-8 有以下编码规则:
1. 如果⼀个字节,最⾼位(第 8 位)为 0,表⽰这是⼀个 ASCII 字符(00 - 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。
2. 如果⼀个字节,以 11 开头,连续的 1 的个数暗⽰这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的⾸字节。
3. 如果⼀个字节,以 10 开始,表⽰它不是⾸字节,需要向前查才能得到当前字符的⾸字节
7、深⼊分析 Java 中的中⽂编码问题(转载)
Java 中需要编码的场景
前⾯描述了常见的⼏种编码格式,下⾯将介绍 Java 中如何处理对编码的⽀持,什么场合中需要编码。
I/O 操作中存在的编码
我们知道涉及到编码的地⽅⼀般都在字符到字节或者字节到字符的转换上,⽽需要这种转换的场景主要是在 I/O 的时候,这个 I/O 包括磁盘 I/O 和⽹络 I/O,关于⽹络 I/O 部分在后⾯将主要以 Web 应⽤为例介绍。下图是 Java 中处理 I/O 问题的接⼝:
Reader 类是 Java 的 I/O 中读字符的⽗类,⽽ InputStream 类是读字节的⽗类,InputStreamReader 类就是关联字节到字符的桥梁,它负责在 I/O 过程中处理读取字节到字符的转换,⽽具体字节到字符的解码实现它由 StreamDecoder 去实现,在 StreamDecoder 解码过程中必须由⽤户指定 Charset 编码格式。值得注意的是如果你没有指定 Charset,将使⽤本地环境中的默认字符集,例如在中⽂环境中将使⽤ GBK 编码。
写的情况也是类似,字符的⽗类是 Writer,字节的⽗类是 OutputStream,通过 OutputStreamWriter 转换字符到字节。如下图所⽰:
同样 StreamEncoder 类负责将字符编码成字节,编码格式和默认编码规则与解码是⼀致的。
如下⾯⼀段代码,实现了⽂件的读写功能:
清单 1.I/O 涉及的编码⽰例
1 String file = "c:/";
2 String charset = "UTF-8";
3 // 写字符换转成字节流
4 FileOutputStream outputStream = new FileOutputStream(file);
5 OutputStreamWriter writer = new OutputStreamWriter(
6 outputStream, charset);
7 try {
8 writer.write("这是要保存的中⽂字符");
9 } finally {
10 writer.close();
11 }
12 // 读取字节转换成字符
13 FileInputStream inputStream = new FileInputStream(file);
14 InputStreamReader reader = new InputStreamReader(
15 inputStream, charset);
16 StringBuffer buffer = new StringBuffer();
17 char[] buf = new char[64];
18 int count = 0;
19 try {
20 while ((count = ad(buf)) != -1) {
21 buffer.append(buffer, 0, count);
22 }
23 } finally {
24 reader.close();
25 }
在我们的应⽤程序中涉及到 I/O 操作时只要注意指定统⼀的编解码 Charset 字符集,⼀般不会出现乱码问题,有些应⽤程序如果不注意指定字符编码,中⽂环境中取操作系统默认编码,如果编解码都在中⽂环境中,通常也没问题,但是还是强烈的不建议使⽤操作系统的默认编码,因为这样,你的应⽤程序的编码格式就和运⾏环境绑定起来了,在跨环境下很可能出现乱码问题。
内存中操作中的编码
在 Java 开发中除了 I/O 涉及到编码外,最常⽤的应该就是在内存中进⾏字符到字节的数据类型的转换,
Java 中⽤ String 表⽰字符串,所以 String 类就提供转换到字节的⽅法,也⽀持将字节转换为字符串的构造函数。如下代码⽰例:
1 String s = "这是⼀段中⽂字符串";
2 byte[] b = s.getBytes("UTF-8");
汉字编码3 String n = new String(b,"UTF-8");
另外⼀个是已经被被废弃的 ByteToCharConverter 和 CharToByteConverter 类,它们分别提供了 convertAll ⽅法可以实现 byte[] 和 char[] 的互转。如下代码所⽰:
1 ByteToCharConverter charConverter = Converter("UTF-8");
2 char c[] = vertAll(byteArray);
3 CharToByteConverter byteConverter = Converter("UTF-8");
4 byte[] b = vertAll(c);
这两个类已经被 Charset 类取代,Charset 提供 encode 与 decode 分别对应 char[] 到 byte[] 的编码和 b
yte[] 到 char[] 的解码。如下代码所⽰:
1 Charset charset = Charset.forName("UTF-8");
2 ByteBuffer byteBuffer = de(string);
3 CharBuffer charBuffer = charset.decode(byteBuffer);
编码与解码都在⼀个类中完成,通过 forName 设置编解码字符集,这样更容易统⼀编码格式,⽐ ByteToCharConverter 和 CharToByteConverter 类更⽅便。
Java 中还有⼀个 ByteBuffer 类,它提供⼀种 char 和 byte 之间的软转换,它们之间转换不需要编码与解码,只是把⼀个 16bit 的 char 格式,拆分成为 2 个 8bit 的 byte 表⽰,它们的实际值并没有被修改,仅仅是数据的类型做了转换。如下代码所以:
1 ByteBuffer heapByteBuffer = ByteBuffer.allocate(1024);
2 ByteBuffer byteBuffer = heapByteBuffer.putChar(c);
以上这些提供字符和字节之间的相互转换只要我们设置编解码格式统⼀⼀般都不会出现问题。
Java 中如何编解码
前⾯介绍了⼏种常见的编码格式,这⾥将以实际例⼦介绍 Java 中如何实现编码及解码,下⾯我们以“I am 君⼭”这个字符串为例介绍 Java 中如何把它以 ISO-8859-1、GB2312、GBK、UTF-16、UTF-8 编码格式进⾏编码的。
清单 2.String 编码
1 public static void encode() {
2 String name = "I am 君⼭";
3 CharArray());
4 try {
5 byte[] iso8859 = Bytes("ISO-8859-1");
6 toHex(iso8859);
7 byte[] gb2312 = Bytes("GB2312");
8 toHex(gb2312);
9 byte[] gbk = Bytes("GBK");
10 toHex(gbk);
11 byte[] utf16 = Bytes("UTF-16");
12 toHex(utf16);
13 byte[] utf8 = Bytes("UTF-8");
14 toHex(utf8);
15 } catch (UnsupportedEncodingException e) {
16 e.printStackTrace();
17 }
18 }
我们把 name 字符串按照前⾯说的⼏种编码格式进⾏编码转化成 byte 数组,然后以 16 进制输出,我们先看⼀下 Java 是如何进⾏编码的。
下⾯是 Java 中编码需要⽤到的类图
图 1. Java 编码类图
⾸先根据指定的 charsetName 通过 Charset.forName(charsetName) 设置 Charset 类,然后根据 Charset 创建 CharsetEncoder 对象,再调⽤ de 对字符串进⾏编码,不同的编码类型都会对应到⼀个类中,实际的编码过程是在这些类中完成的。下⾯是 String. getBytes(charsetName) 编码过程的时序图
图 2.Java 编码时序图
从上图可以看出根据 charsetName 到 Charset 类,然后根据这个字符集编码⽣成 CharsetEncoder,这个类是所有字符编码的⽗类,针对不同的字符编码集在其⼦类中定义了如何实现编码,有了 CharsetEncoder 对象后就可以调⽤ encode ⽅法去实现编码了。这个是 Bytes 编码⽅法,其它的如 StreamEncoder 中也是类似的⽅式。下⾯看看不同的字符集是如何将前⾯的字符串编码成 byte 数组的?
如字符串“I am 君⼭”的 char 数组为 49 20 61 6d 20 541b 5c71,下⾯把它按照不同的编码格式转化成相应的字节。
按照 ISO-8859-1 编码
字符串“I am 君⼭”⽤ ISO-8859-1 编码,下⾯是编码结果:
从上图看出 7 个 char 字符经过 ISO-8859-1 编码转变成 7 个 byte 数组,ISO-8859-1 是单字节编码,中⽂“君⼭”被转化成值是 3f 的 byte。3f 也就是“?”字符,所以经常会出现中⽂变
成“?”很可能就是错误的使⽤了 ISO-8859-1 这个编码导致的。中⽂字符经过 ISO-8859-1 编码会丢失信息,通常我们称之为“⿊洞”,它会把不认识的字符吸收掉。由于现在⼤部分基础的Java 框架或系统默认的字符集编码都是 ISO-8859-1,所以很容易出现乱码问题,后⾯将会分析不同的乱码形式是怎么出现的。
按照 GB2312 编码
字符串“I am 君⼭”⽤ GB2312 编码,下⾯是编码结果:
GB2312 对应的 Charset 是 sun. EUC_CN ⽽对应的 CharsetDecoder 编码类是 sun.
t. DoubleByte,GB2312 字符集有⼀个 char 到 byte 的码表,不同的字符编码就是查这个码表到与每个字符的对应的字节,然后拼装成 byte 数组。查表的规则如下:
c2b[c2bIndex[char >> 8] + (char & 0xff)]
如果查到的码位值⼤于 oxff 则是双字节,否则是单字节。双字节⾼ 8 位作为第⼀个字节,低 8 位作为第⼆个字节,如下代码所⽰:
1 if (bb > 0xff) { // DoubleByte
2 if (dl - dp < 2)
3 return CoderResult.OVERFLOW;
4 da[dp++] = (byte) (bb >> 8);
5 da[dp++] = (byte) bb;
6 } else { // SingleByte
7 if (dl - dp < 1)
8 return CoderResult.OVERFLOW;
9 da[dp++] = (byte) bb;
10 }
从上图可以看出前 5 个字符经过编码后仍然是 5 个字节,⽽汉字被编码成双字节,在第⼀节中介绍到 GB2312 只⽀持 6763 个汉字,所以并不是所有汉字都能够⽤ GB2312 编码。
按照 GBK 编码
字符串“I am 君⼭”⽤ GBK 编码,下⾯是编码结果:
你可能已经发现上图与 GB2312 编码的结果是⼀样的,没错 GBK 与 GB2312 编码结果是⼀样的,由此可以得出 GBK 编码是兼容 GB2312 编码的,它们的编码算法也是⼀样的。不同的是它们的码表长度不⼀样,GBK 包含的汉字字符更多。所以只要是经过 GB2312 编码的汉字都可以⽤ GBK 进⾏解码,反过来则不然。
按照 UTF-16 编码
字符串“I am 君⼭”⽤ UTF-16 编码,下⾯是编码结果:
⽤ UTF-16 编码将 char 数组放⼤了⼀倍,单字节范围内的字符,在⾼位补 0 变成两个字节,中⽂字符也变成两个字节。从 UTF-16 编码规则来看,仅仅将字符的⾼位和地位进⾏拆分变成两个字节。特点是编码效率⾮常⾼,规则很简单,由于不同处理器对 2 字节处理⽅式不同,Big-endian(⾼位字节在前,低位字节在后)或 Little-endian(低位字节在前,⾼位字节在后)编码,所以在对⼀串字符串进⾏编码是需要指明到底是 Big-endian 还是 Little-endian,所以前⾯有两个字节⽤来保存 BYTE_ORDER_MARK 值,UTF-16 是⽤定长 16 位(2 字节)来表⽰的 UCS-2 或 Unicode 转换格式,通过代理对来访问 BMP 之外的字符编码。
按照 UTF-8 编码
字符串“I am 君⼭”⽤ UTF-8 编码,下⾯是编码结果:
UTF-16 虽然编码效率很⾼,但是对单字节范围内字符也放⼤了⼀倍,这⽆形也浪费了存储空间,另外 UTF-16 采⽤顺序编码,不能对单个字符的编码值进⾏校验,如果中间的⼀个字符码值损坏,后⾯的所有码值都将受影响。⽽ UTF-8 这些问题都不存在,UTF-8 对单字节范围内字符仍然⽤⼀个字节表⽰,对汉字采⽤三个字节表⽰。它的编码规则如下:
清单 3.UTF-8 编码代码⽚段
1 private CoderResult encodeArrayLoop(CharBuffer src, ByteBuffer dst){
2 char[] sa = src.array();
3 int sp = src.arrayOffset() + src.position();
4 int sl = src.arrayOffset() + src.limit();
5 byte[] da = dst.array();
6 int dp = dst.arrayOffset() + dst.position();
7 int dl = dst.arrayOffset() + dst.limit();
8 int dlASCII = dp + Math.min(sl - sp, dl - dp);
9 // ASCII only loop
10 while (dp < dlASCII && sa[sp] < '\u0080')
11 da[dp++] = (byte) sa[sp++];
12 while (sp < sl) {
13 char c = sa[sp];
14 if (c < 0x80) {
15 // Have at most seven bits
16 if (dp >= dl)
17 return overflow(src, sp, dst, dp);
18 da[dp++] = (byte)c;
19 } else if (c < 0x800) {
20 // 2 bytes, 11 bits
21 if (dl - dp < 2)
22 return overflow(src, sp, dst, dp);
23 da[dp++] = (byte)(0xc0 | (c >> 6));
24 da[dp++] = (byte)(0x80 | (c & 0x3f));
25 } else if (Character.isSurrogate(c)) {
26 // Have a surrogate pair
27 if (sgp == null)
28 sgp = new Surrogate.Parser();
29 int uc = sgp.parse(c, sa, sp, sl);
30 if (uc < 0) {
31 updatePositions(src, sp, dst, dp);
32 ();
33 }
34 if (dl - dp < 4)
35 return overflow(src, sp, dst, dp);
36 da[dp++] = (byte)(0xf0 | ((uc >> 18)));
37 da[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f));
38 da[dp++] = (byte)(0x80 | ((uc >> 6) & 0x3f));
39 da[dp++] = (byte)(0x80 | (uc & 0x3f));
40 sp++; // 2 chars
41 } else {
42 // 3 bytes, 16 bits
43 if (dl - dp < 3)
44 return overflow(src, sp, dst, dp);
45 da[dp++] = (byte)(0xe0 | ((c >> 12)));
46 da[dp++] = (byte)(0x80 | ((c >> 6) & 0x3f));
47 da[dp++] = (byte)(0x80 | (c & 0x3f));
48 }
49 sp++;
50 }
51 updatePositions(src, sp, dst, dp);
52 return CoderResult.UNDERFLOW;
53 }
UTF-8 编码与 GBK 和 GB2312 不同,不⽤查码表,所以在编码效率上 UTF-8 的效率会更好,所以在存储中⽂字符时 UTF-8 编码⽐较理想。
⼏种编码格式的⽐较
对中⽂字符后⾯四种编码格式都能处理,GB2312 与 GBK 编码规则类似,但是 GBK 范围更⼤,它能处理所有汉字字符,所以 GB2312 与 GBK ⽐较应该选择 GBK。UTF-16 与 UTF-8 都是处理 Unicode 编码,它们的编码规则不太相同,相对来说 UTF-16 编码效率最⾼,字符到字节相互转换更简单,进⾏字符串操作也更好。它适合在本地磁盘和内存之间使⽤,可以进⾏字符和字节之间快速切换,如 Java 的内存编码就是采⽤ UTF-16 编码。但是它不适合在⽹络之间传输,因为⽹络传输容易损坏字节流,⼀旦字节流损坏将很难恢复,想⽐较⽽⾔ UTF-8更适合⽹络传输,对 ASCII 字符采⽤单字节存储,另外单个字符损坏也不会影响后⾯其它字符,在编码效率上介于 GBK 和 UTF-16 之间,所以 UTF-8 在编码效率上和编码安全性上做了平衡,是理想的中⽂编码⽅式。
Java Web 涉及到的编码
对于使⽤中⽂来说,有 I/O 的地⽅就会涉及到编码,前⾯已经提到了 I/O 操作会引起编码,⽽⼤部分 I/O 引起的乱码都是⽹络 I/O,因为现在⼏乎所有的应⽤程序都涉及到⽹络操作,⽽数据经过⽹络传输都是以字节为单位的,所以所有的数据都必须能够被序列化为字节。在 Java 中数据被序列化必须继承 Serializable 接⼝。
这⾥有⼀个问题,你是否认真考虑过⼀段⽂本它的实际⼤⼩应该怎么计算,我曾经碰到过⼀个问题:就是要想办法压缩 Cookie ⼤⼩,减少⽹络传输量,当时有选择不同的压缩算法,发现压缩后字符数是减
少了,但是并没有减少字节数。所谓的压缩只是将多个单字节字符通过编码转变成⼀个多字节字符。减少的是 String.length(),⽽并没有减少最终的字节数。例如
将“ab”两个字符通过某种编码转变成⼀个奇怪的字符,虽然字符数从两个变成⼀个,但是如果采⽤ UTF-8 编码这个奇怪的字符最后经过编码可能⼜会变成三个或更多的字节。同样的道理⽐如整型数字 1234567 如果当成字符来存储,采⽤ UTF-8 来编码占⽤ 7 个 byte,采⽤ UTF-16 编码将会占⽤ 14 个 byte,但是把它当成 int 型数字来存储只需要 4 个 byte 来存储。所以看⼀段⽂本的⼤⼩,看字符本⾝的长度是没有意义的,即使是⼀样的字符采⽤不同的编码最终存储的⼤⼩也会不同,所以从字符到字节⼀定要看编码类型。
另外⼀个问题,你是否考虑过,当我们在电脑中某个⽂本编辑器⾥输⼊某个汉字时,它到底是怎么表⽰的?我们知道,计算机⾥所有的信息都是以 01 表⽰的,那么⼀个汉字,它到底是多少个 0 和 1 呢?我们能够看到的汉字都是以字符形式出现的,例如在 Java 中“淘宝”两个字符,它在计算机中的数值 10 进制是 28120 和 23453,16 进制是 6bd8 和 5d9d,也就是这两个字符是由这两个数字唯⼀表⽰的。Java 中⼀个 char 是 16 个 bit 相当于两个字节,所以两个汉字⽤ char 表⽰在内存中占⽤相当于四个字节的空间。
这两个问题搞清楚后,我们看⼀下 Java Web 中那些地⽅可能会存在编码转换?
⽤户从浏览器端发起⼀个 HTTP 请求,需要存在编码的地⽅是 URL、Cookie、Parameter。服务器端接受到 HTTP 请求后要解析 HTTP 协议,其中 URI、Cookie 和 POST 表单参数需要解码,服务器端可能还需要读取数据库中的数据,本地或⽹络中其它地⽅的⽂本⽂件,这些数据都可能存在编码问题,当 Servlet 处理完所有请求的数据后,需要将这些数据再编码通过Socket 发送到⽤户请求的浏览器⾥,再经过浏览器解码成为⽂本。这些过程如下图所⽰:
图 3. ⼀次 HTTP 请求的编码⽰例
如上图所⽰⼀次 HTTP 请求设计到很多地⽅需要编解码,它们编解码的规则是什么?下⾯将会重点阐述⼀下:
URL 的编解码
⽤户提交⼀个 URL,这个 URL 中可能存在中⽂,因此需要编码,如何对这个 URL 进⾏编码?根据什么规则来编码?有如何来解码?如下图⼀个 URL:
图 4.URL 的⼏个组成部分
上图中以 Tomcat 作为 Servlet Engine 为例,它们分别对应到下⾯这些配置⽂件中:
Port 对应在 Tomcat 的 <Connector port="8080"/> 中配置,⽽ Context Path 在 <Context path="/examples"/> 中配置,Servlet Path 在 Web 应⽤的 l 中的
1 <servlet-mapping>
2 <servlet-name>junshanExample</servlet-name>
3 <url-pattern>/servlets/servlet/*</url-pattern>
4 </servlet-mapping>
<url-pattern> 中配置,PathInfo 是我们请求的具体的 Servlet,QueryString 是要传递的参数,注意这⾥是在浏览器⾥直接输⼊ URL 所以是通过 Get ⽅法请求的,如果是 POST ⽅法请求的话,QueryString 将通过表单⽅式提交到服务器端,这个将在后⾯再介绍。
图 5. HTTPFox 的测试结果
君⼭的编码结果分别是:e5 90 9b e5 b1 b1,be fd c9 bd,查阅上⼀届的编码可知,PathInfo 是 UTF-8 编码⽽ QueryString 是经过 GBK 编码,⾄于为什么会有“%”?查阅 URL 的编码规范 RFC3986 可知浏览器编码 URL 是将⾮ ASCII 字符按照某种编码格式编码成 16 进制数字然后将每个 16 进制表⽰的字节前加上“%”,所以最终的 URL 就成了上图的格式了。
默认情况下中⽂ IE 最终的编码结果也是⼀样的,不过 IE 浏览器可以修改 URL 的编码格式在选项 -> ⾼级 -> 国际⾥⾯的发送 UTF-8 URL 选项可以取消。
从上⾯测试结果可知浏览器对 PathInfo 和 QueryString 的编码是不⼀样的,不同浏览器对 PathInfo 也可能不⼀样,这就对服务器的解码造成很⼤的困难,下⾯我们以 Tomcat 为例看⼀下,Tomcat 接受到这个 URL 是如何解码的。
解析请求的 URL 是在 HTTP11.InternalInputBuffer 的 parseRequestLine ⽅法中,这个⽅法把传过来的 URL 的 byte[] 设置到 Request 的相应的属性中。这⾥的 URL 仍然是 byte 格式,转成 char 是在 org.tor.CoyoteAdapter 的 convertURI ⽅法中完成的:
1 protected void convertURI(MessageBytes uri, Request request)
2 throws Exception {
3 ByteChunk bc = ByteChunk();
4 int length = bc.getLength();
5 CharChunk cc = CharChunk();
6 cc.allocate(length, -1);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论