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

    就到这儿了, 有空再补充

  • 相关阅读:
    python列表(包含列表方法)
    python数据类型和运算符
    python计算机初识
    python运行过程,变量,符号
    python·if语句
    python`while循环
    <select></select> php表单怎么传值
    thinkPhp3 空操作
    thinkphp3.1和3.2的<模板替换>的区别
    thinkphp3 空操作 如果为空会怎么样 empty name=""
  • 原文地址:https://www.cnblogs.com/userrain/p/5321573.html
Copyright © 2020-2023  润新知