人们所理解的文字和符号在这里称作逻辑数据
* 编码是物理数据和逻辑数据的映射关系
* 编码是读写物理数据的契约
* 编码是计算机的"词汇量",不同编码表达范围不同,如: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对应的字符