oracle字符集的一些说明  

客户在ie浏览器里面输入字符,提交后。
这些字符首先根据页面的字符设置进行编码,然后可能被编码成统一的iso8859-1编码,发送到weblogic服务器。

在weblogic服务器端,首先将接收到的字符进行iso8859-1到相应字符集的转换,例如gbk的转换。
注意:weblogic是有字符集的,ie浏览器也有字符集。

weblogic建立连接池的时候,可以选择操作系统端的nls_lang的设置建立连接池,也可以独立的设置连接池的字符集。

weblogic端的程序在转换字符集的时候,是根据weblogic的连接池所设置的字符集进程转换的,可能和Oracle数据库端的字符集的设置不一致。


Unicode、UTF-8 和 ISO8859-1到底有什么区别

本文主要包括以下几个方面:编码基本知识,java,系统软件,url,工具软件等。

在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示"。

2. 编码基本知识

最早的编码是iso8859-1,和ascii编码相似。但为了方便表示各种各样的语言,逐渐出现了很多标准编码,重要的有如下几个。

2.1. iso8859-1

属于单字节编码,最多能表示的字符范围是0-255,应用于英文系列。比如,字母a的编码为0x61=97。

很明显,iso8859-1编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用iso8859-1编码来表示。而且在很多协议上,默认使用该编码。比如,虽然"中文"两个字不存在iso8859-1编码,以gb2312编码为例,应该是"d6d0 cec4"两个字符,使用iso8859-1编码的时候则将它拆开为4个字节来表示:"d6 d0 ce c4"(事实上,在进行存储的时候,也是以字节为单位处理的)。而如果是UTF编码,则是6个字节"e4 b8 ad e6 96 87"。很明显,这种表示方法还需要以另一种编码为基础。

2.2. GB2312/GBK

这就是汉子的国标码,专门用来表示汉字,是双字节编码,而英文字母和iso8859-1一致(兼容iso8859-1编码)。其中gbk编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,gbk是兼容gb2312编码的。

2.3. unicode

这是最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码,包括英文字母在内。所以可以说它是不兼容iso8859-1编码的,也不兼容任何编码。不过,相对于iso8859-1编码来说,uniocode编码只是在前面增加了一个0字节,比如字母a为"00 61"。

需要说明的是,定长编码便于计算机处理(注意GB2312/GBK不是定长编码),而unicode又可以用来表示所有字符,所以在很多软件内部是使用unicode编码来处理的,比如java。

2.4. UTF

考虑到unicode编码不兼容iso8859-1编码,而且容易占用更多的空间:因为对于英文字母,unicode也需要两个字节来表示。所以unicode不便于传输和存储。因此而产生了utf编码,utf编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过,utf编码是不定长编码,每一个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。

注意,虽然说utf是为了使用更少的空间而使用的,但那只是相对于unicode编码来说,如果已经知道是汉字,则使用GB2312/GBK无疑是最节省的。不过另一方面,值得说明的是,虽然utf编码对汉字使用3个字节,但即使对于汉字网页,utf编码也会比unicode编码节省,因为网页中包含了很多的英文字符。

3. java对字符的处理

在java应用软件中,会有多处涉及到字符集编码,有些地方需要进行正确的设置,有些地方需要进行一定程度的处理。

3.1. getBytes(charset)

这是java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。注意字符串在java内存中总是按unicode编码存储的。比如"中文",正常情况下(即没有错误的时候)存储为"4e2d 6587",如果charset为"gbk",则被编码为"d6d0 cec4",然后返回字节"d6 d0 ce c4"。如果charset为"utf8"则最后是"e4 b8 ad e6 96 87"。如果是"iso8859-1",则由于无法编码,最后返回 "3f 3f"(两个问号)。

3.2. new String(charset)

这是java字符串处理的另一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换为unicode存储。参考上述getBytes的例子,"gbk" 和"utf8"都可以得出正确的结果"4e2d 6587",但iso8859-1最后变成了"003f 003f"(两个问号)。

因为utf8可以用来表示/编码所有字符,所以new String( str.getBytes( "utf8" ), "utf8" ) === str,即完全可逆。

3.3. setCharacterEncoding()

该函数用来设置http请求或者相应的编码。

对于request,是指提交内容的编码,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用iso8859-1编码,需要进一步处理。参见下述"表单输入"。值得注意的是在执行setCharacterEncoding()之前,不能执行任何getParameter()。java doc上说明:This method must be called prior to reading request parameters or reading input using getReader()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候,java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。

对于response,则是指定输出内容的编码,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。



作为一个ORACLE DBA,在工作中会经常处理由于字符集产生的一些问题。但是当真正想写一些这方面的东西时,却突然又没有了头绪。发了半天呆,还是决定用两个字符集方面的例子作为切入点,倒不失为一个头绪,说不定在实验的过程中,问题就会一个接着一个的浮现出来。
现在,让我们切入正题。
我用的数据库是oracle10.2.0.3,数据库字符集是al32utf8。
客户端就是同一台机器的windows xp.
下面是演示的例子:

SQL> drop table test purge;
Table dropped.
SQL> create table test(col1 number(1),col2 varchar2(10));
Table created.

--session 1 设置客户端字符集为 zhs16gbk(修改注册表nls_lang项的characterset 为zhs16gbk) 向表中插入两个中文字符。

SQL> insert into test values(1,'中国'); --1为session 1的标记
1 row created.
SQL> commit;
Commit complete.

--session 2 设置客户端字符集 al32utf8(修改注册表nls_lang项的characterset 为al32utf8),与数据库字符集相同。 向表中插入两个和session 1相同的中文字符。

SQL> insert into test values(2,'中国'); --2为session 2的标记
1 row created.
SQL> commit;
Commit complete.

--session 1
SQL> select * from test;
COL1 COL2
---------- --------------------
2 ???
1 中国
--session 2
SQL> select * from test;
COL1 COL2
---------- ----------
2 中国
1 涓浗

从session 1和session 2的结果中可以看到,相同的字符(注意,我指的是我们看到的,显示为相同的字符),在不同的字符集输入环境下,显示成了乱码。
在zhs16gbk字符集的客户端,我们看到了utf8字符集客户端输入的相同的中文变成了乱码-->col1=2的col2字段
在utf8字符集客户端,我们看到zhs16gbk字符集的客户端输入的中文变成了另外的字符 -->col1=1的col2字段
从这个例子里,我们好像感觉到出了什么问题,也可能会联想起现实环境中出现的乱码问题。
问题似乎有了思路,ok,让我们继续把实验做下去:

--session 1 (或者session 2,在这里无所谓)
SQL> select col1,dump(col2,1016) from test;
COL1
----------
DUMP(COL2,1016)
--------------------------------------------------------------------------------
2
Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
1
Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd

我们使用了dump函数,结果看起来很明显了,两个完全相同的字符,在不同的字符集环境下,在数据库中存储成了不同的编码。
对于ZHS16GBK的字符集客户端输入的字符"中国",AL32UTF8使用了3个字节来分别存储一个字符,即:
中--e4,b8,ad
国--e5,9b,bd
我们也可以分别对这个字符进行验证:

--session 1
SQL> select dump('中',1016) from dual;
DUMP('中',16)
--------------------------------------------
Typ=96 Len=3 CharacterSet=AL32UTF8: e4,b8,ad --字符“中” ,和上面直接从数据库中读取存储的字符编码一致。
SQL> select dump('国',1016) from dual;
DUMP('国',16)
--------------------------------------------
Typ=96 Len=3CharacterSet=AL32UTF8: e5,9b,bd --字符“国” ,和上面直接从数据库中读取存储的字符编码一致。

如果使用session 2直接对着两个字符进行测试,一样会得到相同的结果(笔者已经做过测试,这里为了避免冗长,删掉了).

让我们重新来理一下思路,并提出几个问题:
1:为什么显示为相同的字符,存储到数据库中却变成了不同的编码?
2:我们在向数据库中插入数据的时候,oracle究竟做了些什么?
3:操作系统字符集,客户端字符集,数据库字符集究竟是什么关系?

带着这些疑惑,让我们接着做实验,所有的疑团和猜测都会在试验中得以验证。
我的思路是,先取得测试环境的相关参数。
1:windows字符集(codepage)
我们使用chcp命令来获得windows使用的字符集


c:chcp
活动的代码页: 936

通过oracle的官方文档阅读,我们可以将它等同于ZHS16GBK字符集(在安装oracle时,oracle会找到安装平台的字符集,并默认将对应的字符集设置成与它相同,在这里,数据库默认的字符集本身应该是ZHS16GBK,但我强制将它修改为AL32UTF8)。
所以现在我们可以认为,我们使用的操作系统是ZHS16GBk字符集,那么我们在这个环境下输入的字符(也可以说是显示的字符,用的就是这个字符集的编码)。
让我们继续讨论问题。
我们现在要讨论一下客户端字符集究竟是用来做什么的。
我们知道,很多字符集都有自己的编码方式,换句话说,相同的字符,在不同的字符集里对应的编码可能是不一样的。
客户端的字符集就是为了让数据库知道我们传递过去的字符是属于那种字符集,以便于oracle在存储字符时做相应的编码映射。
拿上面的例子来说:
比如字符"中国"
在ZHS16GBK字符集中,它的编码是:d6,d0,b9,fa
在AL32UTF8字符集中,它的编码是:e4,b8,ad,e5,9b,bd
让我们看看例子中两个session输入的相同字符在数据库中存储对应的编码:

SQL> select col1,dump(col2,1016) from t1;
COL1
----------
DUMP(COL2,1016)
--------------------------------------------------------------------------------
2
Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
1
Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd

对于session 1,我们设置的客户端字符集为zhs16gbk。
当我们和数据库建立session后,数据库将认为这个客户端以zhs16gbk字符集编码的方式向数据库发送字符,因为数据库的字符集是al32utf8,所以字符要以这个字符集的编码来存储,此时oracle就会做一个字符编码转换,也就是将字符集zhs16gbk中编码为d6,d0,b9,fa 的字符编码映射成字符集为al32utf8编码为e4,b8,ad,e5,9b,bd,在字符集al32utf8的编码里,e4,b8,ad,e5,9b,bd对应的字符为"中国".
对于session 2,我们设置的客户端字符集为al32utf8。
当我们和数据库建立session后,数据库看到客户端的字符集和数据库的字符集一致,此时oracle将不会再对字符作转换,因为它认为两边的字符编码是一致的。而此时,我们欺骗了数据库,尽管我们将客户端字符集设置为和数据库一致,但是其实我们使用的是zhs16gbk字符集编码(因为此时windows使用的就是这个字符编码),对于字符"中国",zhs16gbk字符集里对应的编码为d6,d0,b9,fa。此时,oracle不加理会的直接将这个编码保存到了数据库中。当我们分别将这两个字符dump出来的时候,就得到下面这样的结果。

SQL> select col1,dump(col2,1016) from test;
COL1
----------
DUMP(COL2,1016)
--------------------------------------------------------------------------------
2
Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
1
Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd

下面我们就进入到了我们最关心的地方,乱码,让我们继续我们的试验。

--session 1
SQL>
SQL> insert into t1 values('中国',1);
1 row created.
SQL> commit;
Commit complete.
SQL> select * from t1;
COL COL2
------------ ----------
中国 1
??? 2
--session 2
SQL> insert into t1 values('中国',2);
1 row created.
SQL> commit;
Commit complete.
SQL> select * from t1;
COL COL2
------ ----------
涓浗 1
中国 2

session 1,我们看到session 2输入的字符"中国"变成了乱码"???",
session 2,我们看到session 1输入的字符"中国"变成了另外的字符"涓浗",
下面我们来分析一下这中间数据库,客户端和操作系统都发生了那些事情。
上面已经讨论了:
session 1 输入的字符"中国" 在数据库中存储的字符编码为”e4,b8,ad,e5,9b,bd".
session 2 输入的字符"中国" 在数据库中存储的字符编码为”d6,d0,b9,fa".
当session 1开始查询时,oracle从表中取出这两个字符,并按照字符集al32utf8和字符集zhs16gbk的编码映射表,将它的转换成zhs16gbk字符编码,对于编码“e4,b8,ad,e5,9b,bd”,它对应的zhs16gbk的字符编码为"d6,d0,b9,fa",这个编码对应的字符为”中国“,所以我们看到了这个字符正常显示出来了,而对于字符集al32utf8字符编码“d6,d0,b9,fa”,由于我们用于显示字符的windows环境使用的是zhs16gbk字符集,而在zhs16gbk字符集里面并没有对应这个编码的字符或者属于无法显示的符号,于是使用了"?"这样的字符来替换,这就是为什么我们看到session 2输入的字符变成了这样的乱码。
当session 2开始查询时,oracle从表中取出这两个字符,由于客户端(nls_lang)和数据库的字符集设置一致,oracle将忽略字符的转换问题,于是直接将数据库中存储的字符返回给客户端。对于编码为"d6,d0,b9,fa"的字符,返回给客户端,而客户端显示所用的字符集正好是zhs16gbk,在这个字符集里,这个编码对应的是"中国"两个字符,所以就正常显示出来了。对于字符编码“e4,b8,ad,e5,9b,bd”,返回到客户端後,因为在zhs16gbk里采用的是双字节存储字符方式,所以这6字节对应了zhs16gbk字符集的3个字符,也就是我们看到的"涓浗".

到现在为止,我想我们基本上搞清楚了为什么日常查询时会遇到乱码的问题。
其实乱码,说到底就是用于显示字符的操作系统没有在字符编码中找到对应的字符导致的,造成这种现象的主要原因就是:
1:输入操作的os字符编码和查询的os字符编码不一致导致出现乱码。
2:输入操作的客户端字符集(nls_lang)和查询客户端字符集(nls_lang)不同,也可能导致查询返回乱码或者错误的字符。

还有一个问题需要解释一下:
在上面的例子中,相同的字符在不同的字符集中对应着不同的字符编码,这个通常称为字符集不兼容或者不完全兼容,比如zhs16gbk和al32utf8,他们存储的ascii码的字符编码都是相同的,但对于汉字却是不同的。
如果两个字符集对于相同的字符采用的相同的字符编码,我们称之为字符兼容,范围大的叫做范围小的字符集的超级。我们通常遇到的zhs16cgb231280,zhs16gbk就是这样的情况,后者是前者的超级。

在实际的环境中除了字符显示之外,还有其他的地方会涉及到字符集问题。比如:
1:exp/imp
2:sql*lorder
3:应用程序的字符输入
......
一个误区:
看到很多人在出现乱码的时候都首先要做的就是将客户端字符集设置和数据库一致,其实这是没有太多根据的。
设想一下,假如数据库字符集是al32utf8,里面存储这一些中文字符,而我的客户端操作系统是英文的,此时我将客户端的nls_lang设置成al32utf8,这样会解决问题吗?这样客户端就能显示中文了吗?客户端就能输入中文了吗?现在客户端是英文的,它的字符集里根本就没有汉字的编码,我们简单的修改一下客户端的字符集又有什么用?前面已经讨论了,这个设置无非就是告诉oracle我将以什么样的字符集与数据库进行数据交换,对于解决乱码问题毫无关系。
正确的做法是将客户端的操作系统改成支持中文字符,并将客户端字符集改成和操作系统一致的字符集,这样才能真正的解决问题。
标签: 暂无标签
oraunix

写了 199 篇文章,拥有财富 1026,被 339 人关注

转播转播 分享分享1 分享淘帖
回复

使用道具

P4 | 发表于 2010-12-17 20:34:32
收藏啦 谢谢老师
回复

使用道具

P4 | 发表于 2012-10-31 20:45:16
收藏啦 谢谢老师
回复

使用道具

P4 | 发表于 2014-1-20 10:43:56
相老师,你说的是NLS_LANG设置和客户端的字符集是一致的,这个客户端是指我客户端工具(如putty 或者secureCRT)还是客户端服务器即操作系统???
回复

使用道具

P5 | 发表于 2014-1-21 09:25:48
收藏啦 谢谢老师
回复

使用道具

P5 | 发表于 2014-1-22 09:31:12
谢谢老师
回复

使用道具

P4 | 发表于 2014-6-13 22:46:01
先收藏了
回复

使用道具

您需要登录后才可以回帖 登录 | 加入社区

本版积分规则

意见
反馈