拷贝: http://wiki.corp.qunar.com/pages/viewpage.action?pageId=110887368
一. 前言
小试牛刀, 大神轻拍
编码无处不在, application/msexcel之内的就不说了, 只说说文本格式的.
二.HTTP
- 对于Http而言, 客户端和服务端可以近似的看做相对的.
- 都会 发出/接受 请求, 并接受/发出 响应
- 在每一方自身都有编码.
- 在传输过程中也有编码.
三. 编码小览
1. 各种编码
1. 编码转换小例子
代码:
String s = "中文";
System.out.println("看, 三字节编码"+Arrays.toString(s.getBytes("UTF-8")));
System.out.println("看, 双字节编码"+Arrays.toString(s.getBytes("gbk")));
System.out.println("看, utf8跟Unicode"+new String(s.getBytes("utf8")));
System.out.println("看, gbk跟unicode"+new String(s.getBytes("gbk")));
System.out.println("看, gbk解码再编码"+new String(s.getBytes("gbk"), "gbk"));
System.out.println("看, ut8解码再gbk编码"+new String(s.getBytes("utf8"), "gbk"));
System.out.println("看, gbk解码再utf8编码"+new String(s.getBytes("gbk"), "utf8"));
System.out.println("看, ISO-8859-1都短了好大一截!"+Arrays.toString(s.getBytes("ISO-8859-1")));
System.out.println("看, 三字节编码变单字节编码"+new String(s.getBytes("UTF8"), "ISO-8859-1"));
输出:
看, 三字节编码 [-28, -72, -83, -26, -106, -121] 看, 双字节编码 [-42, -48, -50, -60] 看, utf8跟Unicode 中文 看, gbk跟unicode ���� 看, gbk解码再编码 中文 看, ut8解码再gbk编码 涓�枃 看, gbk解码再utf8编码 ���� 看, ISO-8859-1都短了好大一截! [63, 63] 看, 三字节编码变单字节编码 ä¸æ–‡
2. 浏览器传输数据
百度的一个请求(搜索 "中文" 二字):
https://www.baidu.com/#ie=utf-8&f=3&rsv_bp=1&rsv_idx=1&tn=baidu&wd=%E4%B8%AD%E6%96%87
看看 uE4B8 uADE6 u9687:代码片段:
byte[] arr = new byte[6];
arr[0] = (byte) 0xE4;
arr[1] = (byte) 0xB8;
arr[2] = (byte) 0xAD;
arr[3] = (byte) 0xE6;
arr[4] = (byte) 0x96;
arr[5] = (byte) 0x87;
System.out.println(new String(arr));
String s = "中文";
System.out.println(Arrays.toString(s.getBytes("UTF-8")));
for (byte bb : s.getBytes("UTF-8")) {
System.out.println(Integer.toHexString(bb));
}
输出结果为:
中文 [-28, -72, -83, -26, -106, -121] ffffffe4 ffffffb8 ffffffad ffffffe6 ffffff96 ffffff87
机智如百度, 直接在请求头的param上面写了utf8编码
---------------------------------------------------------------------------------
因此只要使用合适的编码方式, 哪怕是不兼容的两种字符集, 也是可以互相转换的. 就像是:
System.out.println("中文".getBytes("GBK").length);
System.out.println("中文".getBytes("utf8").length);
System.out.println(new String("中文".getBytes("GBK"))); // 使用了Unicode重新编码, 因为不兼容, 自然乱码了
System.out.println(new String(new String("中文".getBytes("GBK"), "utf8").getBytes("UTF8"), "GBK"));
System.out.println(new String(new String("中文".getBytes("utf8"), "GBK").getBytes("GBK"), "utf8"));
输出
4 6 ���� 锟斤拷锟斤拷 中文
utf8字符集变长, 汉字有三个字节表示的. 当把双字节的汉子用三字节来表示时, 会进行补充, 这样就永久性的多了两个字节, 自然永远也变不回去了.
gbk定长双字节, 汉字使用三字节字符集获取编码并使用双字节字符集编码时, 会变成三个字, 但是再次将其以相同的编码解码, 再使用三字节字符集编码时还是可以编码回去的.
一般在Web上 getBytes("ISO-8859-1")是因为规范就是使用ISO-8859-1来编码, 因此才可以使用它去解码. 并且刚刚好这是可以变回去的
3. 编码的含义
字符集和编码方式 是两种不一样的东西
- 字符集是字符对数字的一种映射
- 编码方式是实现这种映射的一种方式
举个例子:
Unicode是一种字符集合 中文 二字对应的Unicode编码是 u4e2du6587
使用Unicode的一种实现utf8表现为:
11100100 10111000 10101101 11100110 10010110 10000111 ==> 0100 111000 101101 0110 010110 000111 ==> 01001110 00101101 011001 0110000111 ==> 4E 2D 65 87
使用Unicode的一种实现utf32表现为:
0, 0, 78, 45, 0, 0, 101, -121 ==>78 45 101 -121 ==>4E 2D 65 87
此种转换类型对于Big5, GBK等亦然.
至于GBK啥的, 它的字符集, 编码方式怎么叫的, 也难得搞清楚, 姑且不用理会.
有个问题是这些名称到处都在乱叫, 比如 字符集, 字符编码, 编码, 编码方式等等... 导致大部分人都搞不清楚这是什么意思.
其实只要自己懂就可以. 别人怎么说就跟着别人一样的说. 反正我也难得把这些名字叫对.
一定注意的是: 除了ASCII之外, 其他所有的字符集之间都可以理解为不兼容的!!!! 是相互之间不可转换的!!!!!
-----------------------------------------------------------------------
如果想要转换必须通过码表来实现. JAVA见 $JAVA_HOME/jre/lib/charsets.jar
其他平台也一定有自己的实现方式, 因此无需太在意. 有空可以去看看它是怎么配置的.
2. 传输两端
1.html页面
- html页面是文本文件, 文本文件是二进制文件, 但是人类不可读, 因此使用编码方式进行编码
- 不同的编码方式对相同的二进制文件编码出的内容不一样.
- 文本被输入到文件里面, 编码方式已经被固定.
- 记事本, Notepad++等工具, 在另存为的时候, 先 "猜测" 其是什么方式的字符集, 用该字符集对应的编码方式去解码它, 再将其通过码表映射到Unicode, 再在另存为的时候通过码表转化为预期的字符集, 再使用对应的编码方式写入文件
System.out.println(new String(new String("中文".getBytes("GBK"), "GBK"))); System.out.println(new String(new String("中文".getBytes("utf8"), "utf8"))); String fileText; fileText = Files.toString(new File("/home/xia/Sharing/demo.txt"), Charset.forName("GBK")); System.out.println(fileText); Files.write(fileText, new File("/home/xia/Sharing/demo.txt"), Charset.forName("UTF8")); fileText = Files.toString(new File("/home/xia/Sharing/demo.txt"), Charset.forName("UTF8")); System.out.println(fileText);
输出:
中文 中文 汉子 汉子
来自网络的类似来自文件系统的, 只是浏览器不会学习记事本去"猜测"文件的编码方式, 要么使用默认(操作系统/浏览器默认), 要么传输者告知该类型的字符是怎么个编码方式. 这个在HTTP协议里面有规范.
内容有点小多, 每年都在更新. 可以看看去.
jsp页面等等也自有java的规范. 满足规范即可
2.服务器处理
- 服务器端可以理解为跟客户端是相应的. 比如Java使用Unicode来作为运行时的字符集. 这个时候就需要将来自远端的编码转换为Unicode编码. 得到需要的字符进行处理.
- 需要区分的是, 二进制是二进制, 字符是字符, 两者都是自然存在的事物
- 二进制没法人眼直接看出, 只能看到字符, 因此需要将二进制转化为字符.
- 二进制转化为字符的映射方式叫做编码方式. 鉴于世界各地区的差异, 大家使用的字符集都不一定一样, 并且大部分是不相兼容的. 但是一个字符集里面字符永远是一致的.
- 乱码的原因只有一个, 没有知道对对方二进制是被字符由哪一种字符集转化而来的. 即: 字符集不匹配且不兼容
3. 传输途中
- 计算机不认识字符. 只认识 0/1 . 网络传输也是, 只可以传输二进制编码.
- 二进制编码不是凭空得到的, 是被编码方式编码得到的
- 编码方式根据字符集来编码.
- HTML页面或者其他的数据, 有各种编码方式, 转为网络传输流的时候也有各种编码方式, 被解析的时候也有各种编码方式.
- 编码方式之间虽然不兼容, 但是并不以为着使用不兼容的编码方式转化之后就转不回来了, 比如 3.1.2 百度案例.
四. HTTP编码小解
以下单就 Java Servlet而言
请求解析:
GET:
GET /?name=中文 HTTP/1.1 Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, */* Accept-Language: zh-CN User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C) Accept-Encoding: gzip, deflate Host: 192.168.122.152:20000 Connection: Keep-Alive Cookie: JSESSIONID=01F3EDB1F2F48CD6A84780C75130B73B
POST:
POST /?name=%E4%B8%AD%E6%96%87 HTTP/1.1
Host: 192.168.122.152:20000
Connection: keep-alive
Content-Length: 38
Cache-Control: no-cache
Origin: chrome-extension://mkhojklkhkdaghjjfdnphfphiaiohkef
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=B5DE10323B19D16C25BDBD7C7A6FBF8B
涓婇潰鎹�竴琛岋紝 杩欏効涓鸿�姹侭ody
1. GET方式请求
- HTTP请求跨平台.
- Servlet应用在收到HTTP请求的时候已经在Servlet容器中被格式化为了各种字符串. org.apache.coyote.http11.InternalInputBuffer
- 一般情况下HTTP请求 Header里面的东西都是使用 ISO-8859-1编码的, 如果是汉子之内的字符是先用某种编码方式编码了, 再将每个字节拆分成ISO-8859-1(实际上就是单字节)
2. POST方式请求
- 需要注意的是POST的请求也是可以放在请求Url 问号后面的, 没有强制规定不可以
- 这里就比较在意Context-Type Header. 不讨论二进制的. 纯文本类型使用服务器不能支持的Context-Type时是没法解析客户端传递的数据的. 这个时候只能自己获取 InpustStream 来转换.
- Tomcat做了一个很不错的懒加载操作. 使得在第一次调用 getParameter("") 之前没有编码操作(因为请求Body可以使用任意的方式编码), 因此可以在request上设置请求字符集与客户端字符集一致, 就可以正确解码啦
就到这儿了, 有空再补充