编码问题详解


计算机的数据是由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的关系

内存 介质
UnicodewriteUTF
----------->

<------------
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-CharsetJSP文件中Java文件中Class文件中
GB2312D6 D0 CE C4(GB2312)从/u4E2D/u6587(Unicode)
到E4 B8 AD E6 96 87(UTF)
E4 B8 AD E6 96 87(UTF)
ISO8859-1D6 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-charsetServlet源文件中Class文件中等效Unicode码
GB2312D6 D0 CE C4(GB2312)E4 B8 AD E6 96 87(UTF)/u4E2D/u6587
(在Unicode中="中文")
ISO8859-1D6 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 C4IE
2IE把字符串转变成UTF,并送入传输流中E4 B8 AD E6 96 87
3Servlet接收到输入流,用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把新生成的字符串提交给JDBC00 D6 00 D0 00 CE 00 C4
7JDBC检测到数据库内码为ISO8859-100 D6 00 D0 00 CE 00 C4JDBC
8JDBC把接收到的字符串按照ISO8859-1生成字节流D6 D0 CE C4
9JDBC把字节流写入数据库中D6 D0 CE C4
10完成数据存储工作D6 D0 CE C4 数据库
以下是从数据库中取出数的过程
11JDBC从数据库中取出字节流D6 D0 CE C4JDBC
12JDBC按照数据库的字符集ISO8859-1生成字符串,并提交给Servlet00 D6 00 D0 00 CE 00 C4 (Unicode) 
13Servlet获得字符串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准备把字符串输出到客户端
16Servlet根据<Servlet-charset>生成字节流D6D0 CE C4Servlet
17Servlet把字节流输出到IE中,如果已指定<Servlet-charset>,还会设置IE的编码为<Servlet-charset>D6 D0 CE C4
18IE根据指定的编码或默认编码查看结果“中文”(正确显示)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对应的
字符

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值