• GBK,GB3212 Unicode编码问题详解


    计算机的数据是由1和0组成,这种数据在这里称作基本数据或物理数据
    人们所理解的文字和符号在这里称作逻辑数据

    * 编码是物理数据和逻辑数据的映射关系
    * 编码是读写物理数据的契约
    * 编码是计算机的"词汇量",不同编码表达范围不同,如:ASCII和GBK,这是由于排列组合的结果
    * 编码间可以互相转换(通过Unicode),但可能会造成丢失逻辑信息

    例子:
    这里有两个不同的编码规则

     
    编码规则A
    物理数据 逻辑数据
    1000
    1001
    1002
    1003

     

    编码规则B
    物理数据 逻辑数据
    1000
    1001
    1002
    1003


    对于逻辑数据"你好",不同编码有不同的物理数据:
    - 编码A的你好:10001001
    - 编码B的你好:10021003

    对于物理数据"10001001",应用不同的编码,得到的结果不同:
    - 编码A:你好
    - 编码B:他坏

    对于逻辑数据"我们",有的编码不能表达:
    - 编码A的我们:10021003
    - 编码B的我们:<无法表达>



    GB2312
    双字节,定长
    包括一二级汉字和9区符号
    高位低位一样,都是从0xA1~0xFE
    汉字编码范围是0xB0A1~0xF7FE

    GBK
    双字节,定长
    兼容GB2312
    编码范围:0x8140~0xFEFE
    所有字符都可以映射到Unicode2.0

    GB18030-2000(GBK2K)
    收藏少数民族字型
    不定长,包含二字节部分和四字节部分
    二字节部分兼容GBK
    四字节部分是扩充字符,第一第三字节范围:0x81~0xFE,第二第四字节范围:0x30~0x39

    Unicode
    包括所有字符字型
    各地区语言都可与之建立映射
    异种语言的转换是通过Unicode来完成的
    汉字从4E00开始

    UTF(Unicode Text Format,Unicode文本格式)
    1. 如果Unicode的16位的头9位是0,则用一个字节表示。
       这个字节首位位0,省下7位保留原样
       例子:
           源字符:\u0034(0000 0000 0011 0100)
           转化为: 34   (          0011 0100)
    2. 如果Unicode的16位的头5位是0,则用两个字节表示。
       首字节"110"开头,后面5位与源字符出去头5个0后的最高5位相同
       二字节"10"开头,后面6位与源字符低6位相同
       例子:
           源字符:\u025d (0000 0010 0101 1101)
            转化为:c99d (1100 1001 1001 1101)
    3. 如果不符合上述规则,则用三个字节表示
       首字节"1110"开头,后四位为源字符的高四位
       二字节"10"开头,后六位为源字符中间六位
       三自己"10"开头,后六位为源字符低六位
       例子:
           源字符:\u9da7 (1001 1101 1010 0111)
           转化为:e9b6a7 (1110 1001 1011 0110 1010 0111)


    Java中Unicode与UTF的关系

    内存   介质
    Unicode writeUTF
    ----------->

    <------------
    readUTF
    UTF

    IPO模型
    input(CharsetA) -> process(Unicode) -> Output(CharsetB)

    SourceFile(.jsp,.java> -> .class -> output
    - jsp->temp file->class->browser,os,console,db
    - app,servlet->class->browser,os,console,db


    JSP到.class文件的过程

    JSP源文件中有中文字符-"中文",它的的GB2312编码"D6 D0 CE C4"

    Jsp-Charset JSP文件中 Java文件中 Class文件中
    GB2312 D6 D0 CE C4(GB2312) 从\u4E2D\u6587(Unicode)
    到E4 B8 AD E6 96 87(UTF)
    E4 B8 AD E6 96 87(UTF)
    ISO8859-1 D6 D0 CE C4(GB2312) 从\u00D6 \u00D0 \u00CE \u00C4
    到C3 96 C3 90 C3 8E C3 84(UTF)
    C3 96 C3 90 C3 8E C3 84(UTF)
    无(默认=file.encoding)
    假设:ISO8859-1
    同ISO8859-1 同ISO8859-1 同ISO8859-1

    详解第一行:
    1. 编写JSP源文件,存为GB2312格式[D6 D0 CE C4, D6D0=中 CEC4=文]
    2. jspc把JSP源文件转化为临时java文件,并把字符串按照GB2312映射到Unicode,
       并用UTF格式写入Java文件 [E4 B8 AD E6 96 87]
    3. 把临时文件编译成class文件 [E4 B8 AD E6 96 87]
    4. 运行时,先从class文件中用readUTF读出字符串,在内存中是Unicode
       [4E 2D 65 87(在Unicode中,4E2D=中, 6587=文)]
    5. 根据jsp-charset=GB2312把Unicode转化成字节流[D6 D0 CE C4]
    6. 把字节流输出到IE中,并设置IE编码为GB2312(隐藏在HTTP头里)[D6 D0 CE C4]
    7. 用IE"简体中文"查看结果 ["中文"正确显示]

    详解第二行:
    1. 编写JSP源文件,存为GB2312格式[D6 D0 CE C4, D6D0=中 CEC4=文]
    2. jspc把JSP源文件转化为临时java文件,并把字符串按照ISO8859-1映射到Unicode,
       并用UTF格式写入Java文件 [C3 96 C3 90 C3 8E C3 84]
    3. 把临时文件编译成class文件 [C3 96 C3 90 C3 8E C3 84]
    4. 运行时,先从class文件中用readUTF读出字符串,在内存中是Unicode
       [00 D6 00 D0 00 CE 00 C4 (啥都不是!!)]
    5. 根据jsp-charset=ISO8859-1把Unicode转化成字节流[D6 D0 CE C4]
    6. 把字节流输出到IE中,并设置IE编码为ISO8859-1(隐藏在HTTP头里)[D6 D0 CE C4]
    7. 用IE"西欧字符"查看结果 [乱码,其实是4个ASCII字符,但由于大于128,所以看起来象乱码]
    8. 用IE"简体中文"查看结果 ["中文"正确显示]

    Servlet源文件到.class文件的过程
    javac -encoding <Compile-Charset>

    Compile-charset Servlet源文件中 Class文件中 等效Unicode码
    GB2312 D6 D0 CE C4(GB2312) E4 B8 AD E6 96 87(UTF) \u4E2D\u6587
    (在Unicode中="中文")
    ISO8859-1 D6 D0 CE C4(GB2312) E4 B8 AD E6 96 87(UTF) \u00D6 \u00D0 \u00CE \u00C4
    (在每个字节前加了"00")
    无(默认) D6 D0 CE C4(GB2312) 同ISO8859-1 同ISO8859-1

    详解第一行:
    1. 编写Servlet源文件,存为GB2312格式[D6 D0 CE C4, D6D0=中 CEC4=文]
    2. javac -encoding GB2312 把java文件编译成.class文件[E4 B8 AD E6 96 87(UTF)]
    3. 运行时,先从class文件中用readUTF读出字符串,在内存中是Unicode[4E 2D 65 87]
    4. 根据servlet-charset=GB2312把Unicode转化成字节流[D6 D0 CE C4(GB2312)]
    5. 把字节流输出到IE中,并设置IE编码为GB2312(隐藏在HTTP头里)[D6 D0 CE C4(GB2312)]
    6. 用IE"简体中文"查看结果 ["中文"正确显示]
    详解第二行:
    1. 编写Servlet源文件,存为GB2312格式[D6 D0 CE C4, D6D0=中 CEC4=文]
    2. javac -encoding GB2312 把java文件编译成.class文件[C3 96 C3 90 C3 8E C3 84(UTF)]
    4. 运行时,先从class文件中用readUTF读出字符串,在内存中是Unicode
       [00 D6 00 D0 00 CE 00 C4]
    5. 根据jsp-charset=ISO8859-1把Unicode转化成字节流[D6 D0 CE C4(GB2312)]
    6. 用IE"西欧字符"查看结果 [乱码]
    7. 用IE"简体中文"查看结果 ["中文"正确显示]

    class的输出字符串
    内存中是Unicode,但这个Unicode表示什么,要看是从哪种字符集映射过来的
    Unicode"\u00D6 \u00D0 \u00CE \u00C4"表示了什么?
    1. 直接用Unicode码表来对照,得到4个特殊字符
    2. 如果与ISO8859-1映射,则去掉前面的"00",得到 D6 D0 CE C4
    3. 如果与GB2312映射,则可能没有对应上(若对不上,将得到0x3f,也就是问号),即便对应上也是特殊符号
    所以同样的Unicode字符,可以解释成不同的样子

    Class在输出字符串前,会将Unicode的字符串按照某中内码重新生成字节流,
    相当于string.getByte(xCharset)
    * 如果是Servlet,由httpResponse.setContentType(xxx)来指定
    * 如果是JSP,由<% page contentType="" %> 来指定
    * 如果是Java,由file.encoding来指定,默认为ISO8859-1

    输出到DB

    假设DB是ISO8859-1编码

    序号 步骤说明 结果
    1 在IE中输入“中文” D6 D0 CE C4 IE
    2 IE把字符串转变成UTF,并送入传输流中 E4 B8 AD E6 96 87
    3 Servlet接收到输入流,用readUTF读取 4E 2D 65 87(unicode) Servlet
    4 编程者在Servlet中必须把字符串根据GB2312还原为字节流 D6 D0 CE C4
    5 编程者根据数据库内码ISO8859-1生成新的字符串 00 D6 00 D0 00 CE 00 C4
    6 把新生成的字符串提交给JDBC 00 D6 00 D0 00 CE 00 C4
    7 JDBC检测到数据库内码为ISO8859-1 00 D6 00 D0 00 CE 00 C4 JDBC
    8 JDBC把接收到的字符串按照ISO8859-1生成字节流 D6 D0 CE C4
    9 JDBC把字节流写入数据库中 D6 D0 CE C4
    10 完成数据存储工作 D6 D0 CE C4 数据库
    以下是从数据库中取出数的过程
    11 JDBC从数据库中取出字节流 D6 D0 CE C4 JDBC
    12 JDBC按照数据库的字符集ISO8859-1生成字符串,并提交给Servlet 00 D6 00 D0 00 CE 00 C4 (Unicode)  
    13 Servlet获得字符串 00 D6 00 D0 00 CE 00 C4 (Unicode) Servlet
    14 编程者必须根据数据库的内码ISO8859-1还原成原始字节流 D6 D0 CE C4  
    15 编程者必须根据客户端字符集GB2312生成新的字符串 4E 2D 65 87
    (Unicode)
     
    Servlet准备把字符串输出到客户端
    16 Servlet根据<Servlet-charset>生成字节流 D6D0 CE C4 Servlet
    17 Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset> D6 D0 CE C4
    18 IE根据指定的编码或默认编码查看结果 “中文”(正确显示) IE

        步骤:4,5,15,16要编码者自己完成
        4,5   一句话: new String(source.getBytes("GB2312"), "ISO8859-1")   
        15,16 一句话: new String(source.getBytes("ISO8859-1"), "GB2312")


    Q:为什么会有"?"号
    A:转换分两种情况
    1. ACode->Unicode->BCode
    2. Unicode->BCode
    可以看到异种语言的转换是通过Unicode来完成的
    对于(1),当ACode的内容BCode无法映射时,则得到Unicode代码"\ufffd"
    对于(2),如果BCode不能映射,则得到"0x3f",也就是问号
       "李":GBK为"C0EE"->Unicode为"674E"->ISO8859-1 *失败,因为ISO8859-1没有与674E对应的
    字符

  • 相关阅读:
    Windows系统Nessus离线(Offline) 版的安装
    Openstack中keystone与外部LDAP Server的集成
    MySQL常用指令
    关于RequestParam在不同的Spring版本上,接口在controller重载时注解可能失效的踩坑记录
    利用反射注册SpringCache的RedisCacheManager缓存信息
    缩减项目代码中的大面积if策略
    Pentaho Report Designer 报表系统
    五种设计模式的分享
    反射的实践测试
    关于内外网分离情况下双网卡访问速度问题的解决
  • 原文地址:https://www.cnblogs.com/chenyuwang2009/p/2538466.html
Copyright © 2020-2023  润新知