|
5.我的一些实验
为了验证一些字符集转换的细节,我作了下面的实验。为了节约版面,就不用给一条命令,显示一条结果的方式了。下面列表中一条记录各代表一种环境变量,运行程序的组合。表当中char_set表示环境变量NLS_LANG,所有memo字段,最初输入时候都是汉字'测试'(同样的汉字,不同的OS环境可能内码完全不同)。
windows和linux平台数据库字符集都是ZHS16GBK
在windows 2003下面,chcp结果代码页显示936,也就是GBK字符集。char_set字段,后面什么不带表示dos窗口下运行于sqlplus输出的结果。带-W表示dos窗口下启动的sqlplusw,带-WW的表示windows下启动的sqlplusw。
当NLS_LANG=AMERICAN_AMERICA.UTF8时候,显示如下
SQL> select id, char_set, memo, dump(memo) from testc;
ID CHAR_SET MEMO DUMP(MEMO)
--- ------------ -------------- ----------------------------------------
7 US7ASCII ???? Typ=1 Len=4: 63,63,63,63
8 US7ASCII-W ???? Typ=1 Len=4: 63,63,63,63
9 ZHS16GBK 娴嬭瘯 Typ=1 Len=4: 178,226,202,212
10 ZHS16GBK-W 娴嬭瘯 Typ=1 Len=4: 178,226,202,212
11 UTF8 ?锛? Typ=1 Len=3: 63,163,191
12 UTF8-W ?锛? Typ=1 Len=3: 63,163,191
1 UTF8-WW 娴嬭瘯 Typ=1 Len=4: 178,226,202,212
2 US7ASCII-WW 娴嬭瘯 Typ=1 Len=4: 178,226,202,212
8 rows selected.
当NLS_LANG=AMERICAN_AMERICA.ZHS16GBK时候,环境变量和os一致。汉字正常显示了:
SQL> select id, char_set, memo, dump(memo) from testc;
ID CHAR_SET MEMO DUMP(MEMO)
--- ------------ -------------- ----------------------------------------
7 US7ASCII ???? Typ=1 Len=4: 63,63,63,63
8 US7ASCII-W ???? Typ=1 Len=4: 63,63,63,63
9 ZHS16GBK 测试 Typ=1 Len=4: 178,226,202,212
10 ZHS16GBK-W 测试 Typ=1 Len=4: 178,226,202,212
11 UTF8 ?? Typ=1 Len=3: 63,163,191
12 UTF8-W ?? Typ=1 Len=3: 63,163,191
1 UTF8-WW 测试 Typ=1 Len=4: 178,226,202,212
2 US7ASCII-WW 测试 Typ=1 Len=4: 178,226,202,212
已选择8行。
$1.上面2次查询汉字显示不同是因为环境变量NLS_LANG的区别。大家都应该明白了。
$2.注意11, 12行,经过了GBK-》UTF8-》GBK,2个汉字怎么存到硬盘成了3个字节?我这么理解:OS是GBK,最初进入客户端的是4字节的gbk内码。由于客户端环境为UTF8,客户端把这4字节的编码理解成为4字节的UTF8编码(注意这里!!并没有真正的编码转化过程,只是客户端按照环境变量定义给予代码表示的字符不同的解释,即是eygle说的“映射”),前三个字节估计视为一个汉字,最后的字节被视为一个特殊字符。然后进入服务器,由于服务器知道是UTF8->GBK的转换,而前面3字节组成的UTF8字符在GBK中没有对应字符(大字符集到小字符集),所以被简单的替换为?(63),后面的1字节编码,在gbk中找到对应的编码为2个字节。于是最终存入硬盘的编成了63, 163, 191.
$3.id为1,2的2条记录。windows菜单下启动的sqlplusw(char_set字段以-WW结束), 未受到cmd窗口下SET NLS_LANG的影响,而是受注册表(值为GBK)的影响,所以保持不变,看对应的8, 10号记录可以做对比。
最后,当我们试图在NLS_LANG=AMERICAN_AMERICA.UTF8时候输入
SQL> insert into testc values(3, 'UTF8', '测');
ERROR:
ORA-01756: quoted string not properly terminated
出现这种情况可以这么理解:因为我windows2003系统,中文输入的汉字是2字节编码,而NLS_LANG设定为UTF8, 客户端在读取2字节编码时候,发现UTF8中没有对应代码(汉字都是3字节编码),而insert语句又明确说输入的是字符串,所以报错。这里留下一个问题:为什么前面环境变量设置为UTF8时候,输入2个汉字(也就是4字节编码)不报错,难道sqlplus只检查一个字符的情况?
LINUX环境
OS环境(bash窗口下面)为gb2312(即最初输入的汉字编码是双字节的国标码),char_set字段,不带-w表示终端窗口运行的sqlplus,带-w表示是在sqldeveloper中输入的记录(基于java的类似worksheet的软件)
SQL> select id, char_set, memo, dump(memo) from testc order by id;
ID CHAR_SET MEMO DUMP(MEMO)
--- ------------ -------------- ----------------------------------------
1 US7ASCII ?????? Typ=1 Len=6: 63,63,63,63,63,63
2 US7ASCII-W 测试 Typ=1 Len=4: 178,226,202,212
3 ZHS16GBK 娴嬭瘯 Typ=1 Len=6: 230,181,139,232,175,149
4 ZHS16GBK-W 测试 Typ=1 Len=4: 178,226,202,212
5 UTF8 测试 Typ=1 Len=4: 178,226,202,212
6 UTF8-W 测试 Typ=1 Len=4: 178,226,202,212
7 UTF8 测 Typ=1 Len=2: 178,226
8 US7ASCII ??? Typ=1 Len=3: 63,63,63
8 rows selected.
UTF8环境下,输入单个‘测’字正常,显示也正常。
ID CHAR_SET MEMO DUMP(MEMO)
--- ------------ -------------- ----------------------------------------
7 UTF8 测 Typ=1 Len=2: 178,226
但如果我os终端(bash)设定字符编码为UTF8,设ZHS16GBK的环境变量,输入单个字符出错:
SQL> insert into testc values(9, 'ZHS16GBK', '测');
ERROR:
ORA-01756: quoted string not properly terminated
这里因为输入的最初内码为UTF8的3字节,NLS_LANG设定的字符集为ZHSK16GBK,SQLPLUS对输入的字符判断3字节认为不合法。报错。当然,如果我们输入2个汉字,6字节的编码sqlplus会认为是3个汉字编码。当输入3个汉字,又会报错。。。这个问题(还有上面windows环境下的同样问题)我一直没想明白,oracle到底根据什么来判断的。不知道谁能给我答案。
总结:首先明确下“客户端的字符集”概念,对于Oralce来说,客户端并不是指一台计算机,而是指某个客户端程序,可以是一个SQLPLus程序,可以是exp/imp的程序,也可以sqlload,也可以是其他的各种程序,这些程序的所使用的字符集,有些按照会话环境变量,注册表,或者某些参数一样的文件设置的先后顺序来指定。
OS, 客户端,数据库三者字符集关系:我们输入到计算机的信息,都是从OS级别的终端开始的。最开始输入的字符信息,只跟os或者其终端的语言环境有关。当用户在输入字符完毕按下回车后,字符对应的编码被送入客户端程序(这里我们讨论oracle客户端程序,比如sqlplus),客户端程序根据自身的字符集设定(sqlplus受NLS_LANG影响,有的程序可能不同),可能对输入的编码做另外一番“理解”。至于客户端程序到数据库,就是实质上的编码转换过程了。(如果客户端和数据库字符集设定不同,存在转换的话)
另外我们注意到,同样WINDOWS下,不同条件起动的SQLPLUSW表现不同。另外,LINUX下面没有注册表,基于JAVA的SQLDEVELOPER毫无影响。不知道IE环境下是否也相对独立?客户端程序受环境变量影响的条件,估计还得具体环境具体测试才能确定了。 |
|