做一个站内搜索遇到一个问题:
网站全站使用的是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
地址栏里直接输入中文也可以。