相信web开发者都或多或少见过类似如下的url:

http://loaclhost:8088/xxx/%e4%b8%ad%e5%9b%bd?size=4

初次见到url中包含红色字体的部分,心中有一个疑问,这到底是什么鬼?

后来知道了红色字体部分是中文“中国”通过url编码得到,那么疑问又来了,为什么不直接使用“中国”呢?又是通过什么手段将“中国”转换成那么奇怪的额字符串呢?

问题一,中文在计算机的世界中是没有办法被识别的,诸如韩文、日文也是如此,需要用一种统一的标准来标准各种语言文字,就像英文是世界的通用语言一样。于是Unicode出现了,Unicode将地球上所有出现过的文字都记录下来,并用数字与文字一一对应,每一个文字都有了独有的表达方式(比如“中”对应的数字为20013),如此,只认识“0”、“1”的计算机边可以通过数字间接识别文字。众所周知,字节是计算机中最小的计量单位,但是Unicode自身不利于编解码,所以后来在其基础之上诞生了诸如UTF-8、GBK、ISO-8859-1等编码,而其中的UTF-8成为了世界流行通用的编码。上文红色字体部分就是在UTF-8的编码基础之上得到的,具体如何实现见问题二。

问题二,对于中文的url编码可以基于UTF-8或者GBK,由于国际化,这里就讨论基于UTF-8的情况。UTF-8的编码规则可以参考百度百科。中文基本汉字在Unicode的范围为4E00-9FA5,编码成UTF-8为三个字节,对应的编码规则如下:

1110XXXX   

10XX XXXX

10XX XXXX

举一个例子,将“中”编码成UTF-8。“中”对应的Unicode为“20013”,“20013”转换成二进制为“100 111000 101101”,通过上述编码规则编码成UTF-8为“11100100 10111000 10101101”,将红色部分提取出来正好是“20013”的二进制,将UTF-8编码转换成16进制为e4b8ad,每个字节前面添加%,变为%e4%b8%ad,是不是很熟悉的感觉。看看上文url的红色字体部分呢,对的,红色文字部分是“中国”的url编码。

url编码的原理已经介绍完毕了,下面用Java程序实现编码和解码过程。

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class UrlTest {
  public static void main(String[] args) throws UnsupportedEncodingException {
    System.out.println(urlEncode("中国"));    //%e4%b8%ad%e5%9b%bd
    System.out.println(urlDecode("%e4%b8%ad%e5%9b%bd"));    //中国
  }

  static String urlEncode(String str) throws UnsupportedEncodingException {
    StringBuilder sb = new StringBuilder();
    //获得UTF-8编码的字节数组
    byte[] utf8 = str.getBytes("UTF-8");
    for (byte b : utf8) {
      System.out.println(b);
      //将字节转换成16进制,并截取最后两位
      String hexStr = Integer.toHexString(b);
      String temp = hexStr.substring(hexStr.length() - 2);
      //添加%
      sb.append("%");
      sb.append(temp);
    }
    return sb.toString();
  }

  static String urlDecode(String str) throws UnsupportedEncodingException {
    if (str == null) {
      return null;
    }
    if (str.length() % 3 != 0) {
      return null;
    }
    byte[] arr = new byte[str.length() / 3];
    for (int i = 0; i <= str.length() - 3; i += 3) {
      //截取%后两位
      String temp = null;
      if (i == str.length() - 3) {
        temp = str.substring(i + 1);
      } else {
        temp = str.substring(i + 1, i + 3);
      }
      System.out.println(temp);
      //转换成自字节
      arr[i / 3] = (byte) Integer.parseInt(temp, 16);
    }
    System.out.println(Arrays.toString(arr));    //[-28, -72, -83, -27, -101, -67]
    //解码
    return new String(arr, 0, arr.length, "UTF-8");
  }

}