知道为什么要对URL进行 Ecode.Decode而已.
今天看了JAVAEYE上的贴子,印象特别深刻. 使用base64编码,对于SEO的好处
截取一些关键的部分来备份.
原文出处: 作者gembler 确实比较善于动手去研究.赞一个.
http://www.javaeye.com/topic/286240?page=1
很久之前发现淘宝网的搜索关键字在编码后有点可爱,小写英文字母+数字的组合。
想去研究一下,但是后面好像没了回事似的。。记性不好
然后这两天不知道为什么,突然间抽起条脑筋,跑去研究。。。。
--------------------------开始分割线--------------------------
在淘宝网上用关键字“gembler”搜索一下商品,得出以下URL:
- http://search1.taobao.com/browse/0/n-0-----------------g,m5sw2ytmmvza----------------40--commend-0-all-0.htm?at_topsearch=1
http://search1.taobao.com/browse/0/n-0-----------------g,m5sw2ytmmvza----------------40--commend-0-all-0.htm?at_topsearch=1
(留意上面这里:“m5sw2ytmmvza”)
经过一轮 天昏地暗、沙尘滚滚 的分析、研究之后,得出以下结论:
在Base64中,码表是由 [A-Z,a-z,0-9,+,/,=(pad)] 组成的。
然后自己也弄个码表,由 [a-z,2-7(这个2-7是在淘宝网上搜索了n次而得出的结论)] 组成的:
a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
-------------------------------愚蠢分割线---------------------------------
s | t | u | v | w | x | y | z | 2 | 3 | 4 | 5 | 6 | 7 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
在Base64中,是将二进制连成一串,然后再按6位来分割,分割完后在前面补0,这个地球人都知道,不多说了。
而我呢,哈哈,捣蛋一下,按5位来分割,如果刚好够分,那就爽了,
但是,世事往往不如人意,位数不够,那咋办呢?看下面 :)
在Base64中,是用"="来解决的吧。
现在呢,就是在前面补0,然后在后面再补0,其实就是前后补0,
但是,按5位来分割,前面3个0是补定的了,后面的0就看上帝怎么安排了。
举个小例子:字符串 "aaa",(编码/加密)后是 "mfqwc"
二进制: | 01100001 | 01100001 | 01100001 | ||
转换后: | (000)01100 | (000)00101 | (000)10000 | (000)10110 | (000)0001(0) |
十进制: | 12 | 5 | 16 | 22 | 2 |
码表对应: | m | f | q | w | c |
反过来,(解码/解密):
码表对应: | m | f | q | w | c |
十进制: | 12 | 5 | 16 | 22 | 2 |
二进制: | 00001100 | 00000101 | 00010000 | 00010110 | 00000010 |
去掉前3个0后: | 01100 | 00101 | 10000 | 10110 | 00010 |
合并后: | 0110000101100001011000010 |
然后把合并后的串的长度除一下8,发现多了个0:
合并后的二进制码: | 01100001 | 01100001 | 01100001 | 0 |
多了就算了,不要了(其实是在{编码/加密}的分割时候,在分剩的余数的后面补的0)。
然后再将 byte[] 转回字符串
OK!又见回"aaa"了。 :)
*有一点值得注意的是:UTF-8、GBK、GB18030 一般都没什么问题,但是 GB2312 可能字符集不够丰富,繁体字在decode的时候成问号了。
搞了半天,“可视化编辑器”的 Java Code不好使,骨干代码在回帖里贴出................(完整的代码,有兴趣的同志们请见附件!)
编码:
- /**
- * (编码/加密)字节数组
- *
- * @author gembler
- * @version 2008-12-3 下午03:14:43
- *
- * @param keyBytes
- * 需要(编码/加密)的字节数组
- *
- * @return (编码/加密)后的字符串
- */
- private static String encode(byte[] keyBytes) {
- if (keyBytes == null || keyBytes.length < 1) {
- return "";
- }
- /*
- * 合并二进制码,
- * 如:
- * 00101010 11010011 00101101 10100011
- * to
- * 00101010110100110010110110100011
- */
- StringBuilder mergrd = new StringBuilder();
- for (int i = 0; i < keyBytes.length; i++) {
- FormatUtil.formatBinary(keyBytes[i], mergrd);
- }
- /*
- * 以5个bit为单位,计算能分多少组,
- * 如:
- * 00101010110100110010110110100011
- * to
- * 00101 01011 01001 10010 11011 01000 11
- * |
- * (这个11为余下的位)
- */
- int groupCount = mergrd.length() / FIVE_BIT;
- // 计算余下的位数
- int lastCount = mergrd.length() % FIVE_BIT;
- // 类似数据分页的算法,有余数的情况下需要加 1。
- if (lastCount > 0) {
- groupCount += 1;
- }
- /*
- * (编码/加密)
- */
- StringBuilder sbEncoded = new StringBuilder();
- // 循环所需的条件
- int forMax = groupCount * FIVE_BIT;
- // 每次递增5位来截取
- for (int i = 0; i < forMax; i += FIVE_BIT) {
- // 结束点
- int end = i + FIVE_BIT;
- /*
- * 如果结束点比已合并的二进制码串的长度要大,
- * 相当于有余数,
- * 并且表示当前循环到了(已合并的二进制码串的长度 % FIVE_BIT)的那一截。
- */
- // 标记是否到了余数的那一截
- boolean flag = false;
- if (end > mergrd.length()) {
- /*
- * 如果结束点比已合并的二进制码串的长度要大,
- * 结束点需要被重设为:
- * 已合并的二进制码串的长度,等价于(i + lastCount). 并且重设标记。
- */
- end = (i + lastCount);
- flag = true;
- }
- // 截取
- String strFiveBit = mergrd.substring(i, end);
- // 截取后从二进制转为十进制
- int intFiveBit = Integer.parseInt(strFiveBit, BINARY);
- if (flag) {
- /*
- * 如果结束点比已合并的二进制码串的长度要大,
- * 或者是到了余数的那一截:
- * 需要左移操作,假设余下的二进制位为:11,
- * 那么需要从后面补0,左移操作后为 (000)11(000)
- */
- intFiveBit <<= (FIVE_BIT - lastCount);
- }
- // 利用该十进制数作为码表的索引获取对应的字符,并追加到sbEncoded
- sbEncoded.append(CODEC_TABLE.charAt(intFiveBit));
- }
- return sbEncoded.toString();
- }
- /**
- * (解码/解密)字符串
- *
- * @author gembler
- * @version 2008-12-3 下午03:15:00
- *
- * @param code
- * 需要(解码/解密)的字符串
- * @param characterSet
- * 字符集
- *
- * @return (解码/解密)后的字符串
- */
- public static String decode(String code, String characterSet) {
- if (code == null || code.length() < 1) {
- return "";
- }
- /*
- * 拆除每一个字符,从码表里获取相应的索引。
- */
- StringBuilder sbBinarys = new StringBuilder();
- for (int i = 0; i < code.length(); i++) {
- // 从码表里获取相应的索引
- int index = getCodecTableIndex(code.charAt(i));
- // 将十进制的索引转换为二进制串
- String indexBinary = Integer.toBinaryString(index);
- // 去掉前3个0,并且追加到sbBinarys
- FormatUtil.formatBinary(indexBinary, sbBinarys, FIVE_BIT);
- }
- /*
- * 按8个bit拆分,剩下的余数扔掉。
- * 扔掉的余数是在(编码/加密)的分割时候,在分剩的余数的后面补的0
- */
- byte[] binarys = new byte[sbBinarys.length() / EIGHT_BIT];
- for (int i = 0, j = 0; i < binarys.length; i++) {
- // 每8个bit截取一份
- String sub = sbBinarys.substring(j, j += EIGHT_BIT);
- // 将截取下来的二进制串转换为十进制
- Integer intBinary = Integer.valueOf(sub, BINARY);
- binarys[i] = intBinary.byteValue();
- }
- String decoded = null;
- if (characterSet == null || characterSet.length() < 1) {
- // 采用默认语言环境的 character set。
- decoded = new String(binarys);
- } else {
- try {
- // 采用指定的 character set。
- return new String(binarys, characterSet);
- } catch (UnsupportedEncodingException e) {
- // ignore...
- }
- }
- return decoded;
- }
测试:
- BufferedReader br = new BufferedReader(new InputStreamReader(
- System.in));
- while (true) {
- System.out.print("输入字符号串:");
- String in = br.readLine();
- if ("exit".equalsIgnoreCase(in)) {
- break;
- }
- String enCode = Codec.encode(in);
- String deCode = Codec.decode(enCode);
- System.out.println();
- System.out.println("original: " + in);
- System.out.println("encode: " + enCode);
- System.out.println("decode: " + deCode);
- System.out.println();
- }
BufferedReader br = new BufferedReader(new InputStreamReader(
System.in));
while (true) {
System.out.print("输入字符号串:");
String in = br.readLine();
if ("exit".equalsIgnoreCase(in)) {
break;
}
String enCode = Codec.encode(in);
String deCode = Codec.decode(enCode);
System.out.println();
System.out.println("original: " + in);
System.out.println("encode: " + enCode);
System.out.println("decode: " + deCode);
System.out.println();
}
测试结果:
输入:JavaEye论坛频道
输出:
original: JavaEye论坛频道
encode: jjqxmykfpfs4fw6mwpdllnoa
decode: JavaEye论坛频道
然后跑去淘宝网敲入"JavaEye论坛频道"搜索。
得出:
http://search1.taobao.com/browse/0/n-g,jjqxmykfpfs4fw6mwpdllnoa----------------40--commend-0-all-0.htm?at_topsearch=1
比较一下:
jjqxmykfpfs4fw6mwpdllnoa - console输出
jjqxmykfpfs4fw6mwpdllnoa - 淘宝网URL
下面是一些高手的回答:
我觉得,首先,为什么不用普通的参数,我想是这样,淘宝的服务器压力很大,对应用的性能要求应该很苛刻,搜索的时候你看起来你输入只是一个关键字,实际上 远远不止,有几十个参数,如果用一组事先约定好位置的数据来表示各种参数,服务器不用解析那么多的字符串,不用处理那么多的参数名=参数,虽然开发维护起 来会增加一些工作量,但是服务器压力会小一些,流量会小一些。 |
至于编码方式,回应楼上某位同学的话,base64编码不是淘宝自己搞出来的,是email刚出现时特殊历史时期的产物,具体的可以google一下,base64编码后数据量比urlencode少60%左右 |
LZ确实很厉害。我觉得TB好像习惯对URL进行BASE64。URL有保密信息的话会再来个BF加密。使用BASE64怕非英文的URL解码后会产生服务器操作的命令。BASE64的编码快,解码也快。 |
|
我来给大家剖析一下吧,淘宝的原来一帮人是从阿里出来的。而阿里最强悍的就是SEO。 |
|
这样大家是否有点清楚了。 |
|
SEO对URL还是非常的看重的,而url中如果包含太多中文等字符的话,对SEO还是不友好。 |
|
那么搞一个英文加字符的编码对机器来说还是比较容易好搞的。 |
|
至于非要自己搞一个,那其实这也不是什么特别难的东东,增加一些神秘感不是更好。 |
跟效率没有关系, 还有URL的并不能变短, BASE64编码好处是字符编码范围有限度。这么做完全让复杂的URL看上去好看点, 方便SEO。 其他的没有什么问题。 另外一个是隐藏URL的具体参数的含义。 这个需要有个强大的正则表达规则来解析URL, 开发的时候,还是URL?PARAM=XX的形式。 这个问题没有好追究的, 一个简易的URL人们总是比较喜欢而已。 |
另外, 还有一个开发经验的问题, URL实际上大小写, 在EMAIL中发送, 都存在各种问题, 完全使用ASCII也可以避免汉字编码问题。 反正这个URL都通过一个专用URL生成的工具完成的。 URL的处理经验是通过血的教训得到的, 那些RFC推荐的规范未必适合做应用。 |
不是说这个方法是完美的, 可是能解决很多问题。 网站的性能不是在文本处理上, 目前的CPU的计算能力已经超过很多开发人员的想象了。 |
对了, 我不是TAOBAO的, 别PM问我了。。。。 |
<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> <script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>