做一个站内搜索遇到一个问题:

网站全站使用的是UTF-8编码,所以get请求的URL也用UTF-8编码,服务器端用UTF-8解码。这种情况下,用户直接在表单里输入提交过来搜索,是没有问题的。但如果用户直接在浏览器地址栏里把关键词给改了,提交过来,或者从浏览器地址栏的下拉提示列表里点击过来,URL编码就不确定了。这个和操作系统语言以及浏览器相关。

ie默认情况下,对在地址栏里输入的URL路径里的中文是用utf-8编码的,但对get参数不会自动编码,会直接把原始字符串发过去。

其他浏览器都会对地址栏里输入的get参数进行编码,编码方式和操作系统环境语言相关。

研究了一下几个大的搜索引擎是怎么处理这个问题的。

1.百度,搜狗等国内搜索引擎

这些搜索引擎的默认编码都是gb2312的,所以一般用户不会遇到这种问题。但我的系统是linux,系统编码是zh_CN.UTF-8的,就会遇到这种问题。

如百度:

gb2312编码:

http://www.baidu.com/s?wd=%D6%D0%B9%FA

utf-8编码

http://www.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD

所以国内的搜索引擎基本没处理这个问题。应该认为这样的用户比较少,可以不予考虑吧。

2. google.com

google.com默认编码是utf-8,也只接受utf-8编码的地址,否则会出现乱码。google.com面对的是全球用户,不会专门为了中国用户而做特殊处理,可以理解。

gb2312

http://www.google.com/search?q=%D6%D0%B9%FA

utf-8

http://www.google.com/search?q=%E4%B8%AD%E5%9B%BD

3.google.cn

google.cn的默认编码也是utf-8。但它针对的是中文用户。而中文用户的大多数操作系统是中文的windows,浏览器的默认编码也一般是gb2312,所以这个问题必须考虑。google.cn能同时兼容两种编码。

gb2312:

http://www.google.cn/search?q=%D6%D0%B9%FA

utf-8:

http://www.google.cn/search?q=%E4%B8%AD%E5%9B%BD

google.cn是怎么做到的?

有人说数query里的%号,utf-8的一个汉字是3个字节,所以有三个%,而gb2312的编码的汉字是2个字节,2个%号。但遇到2和3的倍数呢?比方6个字节,是当3个gb2312的汉字处理呢还是2个utf-8的汉字处理呢?

于是在网上找了一下UTF-8的编码规则:

UTF-8的编码规则很简单,只有二条:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
  2. 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的用x表示的二进制位,全部用这个符号的unicode码填充。

下表总结了编码规则,字母x表示可用编码的位。

​ Unicode符号范围 | UTF-8编码方式 ​ (十六进制) | (二进制) ​ ——————–+——————————————— U-00000000 - U-0000007F: 0xxxxxxx
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

汉字的unicode符号范围是4e00-9fff(其中9FA6~9FFF还是空码),正好在第三行的范围内,也就是说每个汉字在UTF-8中都会转换为3个字节。其中第一个字节在11100000(0xE0)-11101111(0xEF)范围内,后两个字节在10000000(0x80)-10111111(0xBF)范围内。

于是办法产生了:

private static boolean isUTF8Query(String q)
	    throws UnsupportedEncodingException {
	byte[] bytes = q.getBytes("ISO-8859-1");
	for (int i = 0; i < bytes.length; i++) {
           //java 中的byte是有符号的,大于127的都为负数。先转换为int型正数再比较。
	    int first = 0x100 + bytes[i];
          //寻找查询关键词中的第一个中文字符的第一个字节
	    if (first < 0xE0 || first > 0xEF) {
		continue;
	    }
	    if (i + 2 < bytes.length) {
		int second = 0x100 + bytes[i + 1];
		int third = 0x100 + bytes[i + 2];
		if (second >= 0x80 && second <= 0xBF && third >= 0x80
			&& third <= 0xBF) {
		    return true;
		}
	    }
	}
	return false;
    }

使用:

String q= request.getParameter("q");
q = isUTF8Query(q)?new String(q.getBytes("ISO-8859-1"),"UTF-8"):new String(q.getBytes("ISO-8859-1"),"GBK");
	

也有人用搜索queryString里的 %E 的个数的方式做这件事情。不过那样一方面不太准确,另一方面ie不会自动把用户在地址栏输入的get查询中的中文URLEncode,那样的情况下服务器端获取的queryString里没有%,

服务器端的问题:

用这种方式的时候,服务器端不能让服务器自动解码。如果是tocmat的话,在connector上不能设置URIEncoding=”UTF-8”,否则tomcat用utf-8解码后,再还原为原始字符串的字节比较麻烦。

如果是其他语言,比方php或者python,也不能让apache自动解码。

演示地址:

gb2312编码:

http://so.1ting.com/song.do?q=%C1%F5%B5%C2%BB%AA

utf-8编码:

http://so.1ting.com/song.do?q=%E5%88%98%E5%BE%B7%E5%8D%8E

地址栏里直接输入中文也可以。