java中编码问题

拷贝: http://wiki.corp.qunar.com/pages/viewpage.action?pageId=110887368

{toc}

一. 前言

小试牛刀, 大神轻拍

编码无处不在, 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协议里面有规范.

https://www.w3.org/Protocols/

内容有点小多, 每年都在更新. 可以看看去.

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上设置请求字符集与客户端字符集一致, 就可以正确解码啦 

就到这儿了, 有空再补充

原文地址:https://www.cnblogs.com/userrain/p/5321573.html