编码问题详解

来源:互联网 发布:Java用户密码加密 编辑:程序博客网 时间:2024/05/21 03:28


计算机的数据是由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对应的
字符