1.一些廢話:
因為前些天工作中遇到一些字符集相關的問題。想到以前也遇到過類似狀況,不過一直沒真正搞清楚原理。所以干脆花了一個通宵時間,ITPUB上相關文章基本看完。總算明白了個七七八八。看到類似問題被反復問。就萌發了寫個總結帖子的念頭,一來算自己學習的一個總結。二來也算造福大眾吧。
首先,之前ITPUB已經有數位先輩總結貼:
http://www.eygle.com/index-special.htm
eygle的網站字符集問題專題帖。一共7篇文章,貌似發表在ITPUB出版物上。從字符集基本知識講到內部詳細處理,必看!
http://www.itpub.net/showthread.php?threadid=276524
jeffli73的字符集問題分析,包括了字符集的基礎知識和幾個實驗。幾個實驗如果能真正理解了,字符集問題也就很少能困擾你了。
http://www.itpub.net/showthread. ... =%D7%D6%B7%FB%BC%AF
重點在Toms_zhang的最后幾個回貼。雖然是主講JAVA環境下字符集轉換問題,但在理解ORACLE字符集轉化上頗有值得借鑒之處。而且現在也常遇到數據庫,基于JAVA的WEB服務攪和在一起的狀況,這就將字符集問題更加復雜化了。
因為有上面這些相關帖子,我這文章的內容算一些零碎知識的補充,加上自己實驗和一些理解上的心得。希望以后的朋友們遇到類似問題看了我的帖子(其實主要是看我上里的鏈接)就能自己搞定大部分狀況。
2.字符編碼歷史,和常見字符集簡介
很多是照搬 http://www.chedong.com/tech/hello_unicode.html 的,有興趣的可以看看原貼。
a.最基本的字符集
一個 character set (字符集)是一組符號和編碼,比如,大家計算機原理第一課學的ASCII字符集,包括的字符有:數字,大小寫字母,分號、換行之類的符號,編碼方式是用一個7bit表示一個字符 (A的編碼是65,b的編碼是98)。ASCII只規定了英文字母的編碼,非英文語言不能用ASCII編碼表示,為此,不同的國家,都為自己的語言做了編碼。這就有了字符集第一步的擴展:
b.英文和歐洲其他語言的單字節字符集(SingleByte Charsets):
ISO(國際標準化組織)制定了一個針對歐洲多國的字符集標準系列ISO-8859,可以將該系列的字符集都想象成一個:2^8 = 16 * 16 = 256個格子的棋盤,這樣所有的西文字符(英文)用這樣一個16×16的坐標系就基本可以覆蓋全了。而英文實際上只用其中小于128(/x80)的部分就夠了。利用大于128部分的空間的不同定義規則形成了針對其他歐洲語言的擴展字符集:ISO-8859-2 ISO-8859-4等。不同國家使用復合自己國家語言的字符集標準。所有這些國家的字符集都有公共交集ASCII。
在oracle 7.3.4這樣老的系統中,很常見的就是WE8ISO8859P1(Western European 8-bit ISO 8859 Part 1)。記得即使中文系統好多也用的這個,忘了是不是默認值了。
c.亞洲語言和雙字節字符集
漢字這么多,用這么一個256格的小棋盤肯定放不下,所以要區別成千上萬的漢字解決辦法就是用2個字節(坐標)來定位一個“字”在棋盤上的位置,將以上規則做一個擴展:
* 如果第1個字符是小于128(/x80)的仍和英文字符集編碼方式保持兼容;
* 如果第1個字符是大于128(/x80)的,就當成是漢字的第1個字節,這個自己和后面緊跟的1個字節組成一個漢字;
其結果相當于在位于128以上的小棋格里每個小棋格又劃分出了一個16×16的小棋盤。這樣一個棋盤中的格子數(可能容納的字符數)就變成了128 + 128 * 256。按照類似的方式有了簡體中文的GB2312標準,繁體中文的BIG5字符集和日文的SJIS字符集等,GB2312字符集包含大約有六仟多個常用簡體漢字。所有這些從ASCII擴展式的編碼方式中:英文部分都是兼容的,但擴展部分的編碼方式是不兼容的,雖然很多字在3種體系中寫法一致(比如“中文”這2個字)但在相應字符集中的坐標不一致,所以GB2312編寫的頁面用BIG5看就變得面目全非了。而且有時候經常在瀏覽其他非英語國家的頁面時(比如包含有德語的人名時)經常出現奇怪的漢字,其實就是擴展位的編碼沖突造成的。我把GBK和GB18030理解成一個小UNICODE:GBK字符集是GB2312的擴展(K),GBK里大約有貳萬玖仟多個字符,除了保持和 GB2312兼容外,繁體中文字,甚至連日文的假名字符也能顯示。而GB18030-2000則是一個更復雜的字符集,采用變長字節的編碼方式(2~4字節),能夠支持更多的字符。
oracle8,8i中,中文環境默認是ZHS16GB2312?還是ZHS16GBK?誰比較清楚滴?
c.橫空出世UNICODE
ASCII(英文) ==> 西歐文字 ==> 東歐字符集(俄文,希臘語等) ==> 東亞字符集(GB2312 BIG5 SJIS等)==> 擴展字符集GBK GB18030這個發展過程基本上也反映了字符集標準的發展過程,但這么隨著時間的推移,尤其是互聯網讓跨語言的信息的交互變得越來越多的時候,太多多針對本地語言的編碼標準的出現導致一個應用程序的國際化變得成本非常高。尤其是你要編寫一個同時包含法文和簡體中文的文檔,這時候一般都會想到要是用一個通用的字符集能夠顯示所有語言的所有文字就好了,而且這樣做應用也能夠比較方便的國際化,為了達到這個目標,即使應用犧牲一些空間和程序效率也是非常值得的。UNICODE就是這樣一個通用的解決方案。你可以把UNICODE想象成這樣:讓所有的字符(包括英文)都用2個字節(2個8位)表示,這樣就有了一個2^(8*2) = 256 * 256 = 65536個格子的大棋盤。在這個棋盤中,這樣中(簡繁)日韓(還包括越南)文字作為CJK字符集都放在一定的區位內,為了減少重復,各種語言中寫法一樣的字共享一個“棋格”。
d.為什么還要有UTF-8?
畢竟互聯網70%以上的信息仍然是英文。如果連英文都用2個字節存取(UCS-2),空間浪費不就太多了?所謂UTF-8就是這樣一個為了提高英文存取效率的字符集轉換格式:Unicode Transformation Form 8-bit form。用UTF-8,UNICODE的2字節字符用變長個(1-3個字節)表示:1. 對英文,仍然和ASCII一樣用1個字節表示,這個字節的值小于128(/x80);2. 擴展的ASCII字符(主要是西歐),第一字節用C2 - DF之間的范圍,雙字節表示。3.對其他語言,比如亞洲語系,還有各種特殊符號,使用3個字節表示;
因此,在應用中程序處理過程中所有字符都是16位(雙字節),但在存取轉換成字節流時使用UTF-8格式轉換,對于英文字符來說和原來用ASCII方式存取時相比大小仍然是一樣的,而對中文來說和原來的GB2312編碼方式相比,大小為:(3字節/2字節)=1.5倍
ps:UTF8標準也在變化。最新的標準,UTF8可以是1-4個字符。4個字符是為了將來可能的新字符,符號進行擴展。(用10g文檔上來說,就是:historic characters; musical symbols; mathematical symbols)為此oracle也定義了新的字符集AL32UTF8,也就是說其實UTF8的國際化標準就一個,只是版本在變化。而oracle公司根據不同國際標準版本有了UTF8和AL32UTF8兩個具體在數據庫上實現的字符集。UTF8不支持4字節擴展的。
總結:對于不同字符集關系的理解,我們可以用個比喻來說明。如果說一個字符集就是一個公司的倉庫,字符的編碼就是貨架編號,字符的實際值就是貨物本身。那么:
ASCII就是最小的倉庫,就是127個貨架,放了127樣貨物,比如 100號貨架上放的是雨傘;
西歐擴展iso-8859系列,這些倉庫都比A記(ASCII)多了差不多一倍的貨物,而且編號1~127的貨物也和A記一模一樣。不過如果你去看160號貨架的話,可能這家放的茶杯,那家就是毛巾了;
ZHS16GBK,這是個百貨公司 ,有上萬的貨物,即是如此還是沒有忘本,你如果找100號貨架,肯定還是能拿到雨傘;
UNICODE,他的口號是別人有的我也要有。所以修了有65535個貨架的大倉庫。雖然Z記有的東西他也全有,不過貨物放的貨架(編碼)全不一樣了。
UTF8,真正的托拉斯,不但做到了人無我有(包括了現在所有的字符集的字符),還想千秋萬代一桶漿糊(為以后擴展留了余地),和U記類似,東西非常多,貨物編號除了1~127號全和別人不一樣了。
3.oracle中字符集的轉化
常見有討論三種情況:
a. exp/imp時候
在這種情況下,可能存在四方字符集差異:源數據庫字符集、Export過程中用戶會話字符集、Import過程中用戶會話字符集、目標數據庫字符集。具體可能的轉化過程eygle帖子里面有講。就不多說了。記住一條:為了確保Export、Import過程中,Oracle字符集不發生轉換或正確轉換,最好在進行這個過程前,檢查一下源數據庫字符集與Export 用戶會話字符集是否一致,源數據庫字符集與目標數據庫字符集是否一致,目標數據庫字符與Import用戶會話字符集是否一致。如果能夠保證這四個字符集是一致的,則在Export、Import過程中,Oracle字符集就不用發生轉換。
b.客戶端會話輸入字符,最終進入服務器存儲的過程
這個在上面說的帖子里面也詳細講述了。我后面會有個實驗做進一步的細節補充說明。
c.SQLLDR時候
按SQLLOADER的原理來看,應該和字符集沒有關系。如果CLIENT和SERVER端不一致,查出來的漢字當然是亂碼,但實際上已經LOADER成功,只是顯示問題。
4.如何查看字符集
a.如何查看服務器的字符集
通過初始化文件InitXXXX.ora文件進行查看;
借助SQL語句查看: SELECT NAME,VALUE$ FROM SYS.PROPS$ WHERE NAME=‘NLS_CHARACTERSET’
有很多種方法可以查出oracle server端的字符集,比較直觀的查詢方法是以下這種:
SQL>select userenv(‘language’) from dual;
b、如何查詢dmp文件的字符集
用oracle的exp工具導出的dmp文件也包含了字符集信息,dmp文件的第2和第3個字節記錄了dmp文件的字符集。如果dmp文件不大,比如只有幾M或幾十M,在windows下面可以用UltraEdit打開(16進制方式),看第2第3個字節的內容,如0354,然后用以下SQL查出它對應的字符集:
SQL> select nls_charset_name(to_number('0354','xxxx')) from dual;
UNIX下面可以用FTP到WINDOWS下面用WINHEX或者UE看,特別注意FTP方式要BIN。據說winHEX比UltraEdit打開大文件更快。
如果dmp文件很大,比如有2G以上(這也是最常見的情況),用文本編輯器打開很慢或者完全打不開,可以用以下命令(在unix主機上):
cat exp.dmp |od -x|head -1|awk '{print $2 $3}'|cut -c 3-6
然后用上述SQL也可以得到它對應的字符集。
c、查詢oracle client端的字符集
在windows平臺下,就是注冊表里面相應OracleHome的NLS_LANG。還可以在dos窗口里面自己設置,比如:
set nls_lang=AMERICAN_AMERICA.ZHS16GBK,這樣就只影響這個窗口里面的環境變量。
在unix平臺下,就是環境變量NLS_LANG。使用命令 echo $NLS_LANG 查看
如果檢查的結果發現server端與client端字符集不一致,請統一修改為同server端相同的字符集。
總結:字符集子集向其超集轉換是可行的,如US7ASCII向WEISO8859P1轉換;而字符集超類向字符集子集進行轉換時,可能會損失部分數據。在理解了字符集轉化原則的基礎上,我們可以知道:1.極端的情況,即使完全不兼容的字符集,可能也不會有數據丟失!只包含英文字符數據的雙字節字符集可向單字節字符集正確轉換,如ZHS16GBK(English Only)向US7ASCII(因為實在沒有什么好丟失滴 )。2.編碼范圍相同的單字節字符集之間通常可以進行相互轉換。比如iso-8859系列。
請注意,這里所說的沒有數據損失,是指一種字符集A轉換成另一種字符集B之后,可以再從字符集B正確轉換成字符集A或字符集B能夠正確表示字符集A中轉換過來的數據。
ps:一個英文字母是一個字符,一個中文漢字是幾個字符呢?我們知道,os字符集是gbk環境下,一個中文漢字是雙字節字符,但它算做幾個字符與其數據庫字符集有關。如果數據庫字符集使用單字節US7ASCII,則一個中文漢字是二個字符;如果數據庫字符集使用雙字節字符集ZHS16GBK,則一個中文漢字被視為一個字符。有關這一點可以使用 Oracle的函數Substr得到證明。
使用US7ASCⅡ字符集時:
Select substr(‘東北大學’,1,2) from dual;
語句執行結果返回‘東’。
使用ZHS16GBK字符集時:
Select substr(‘東北大學’,1,2) from dual;
語句執行結果返回‘東北’。
因為前些天工作中遇到一些字符集相關的問題。想到以前也遇到過類似狀況,不過一直沒真正搞清楚原理。所以干脆花了一個通宵時間,ITPUB上相關文章基本看完。總算明白了個七七八八。看到類似問題被反復問。就萌發了寫個總結帖子的念頭,一來算自己學習的一個總結。二來也算造福大眾吧。
首先,之前ITPUB已經有數位先輩總結貼:
http://www.eygle.com/index-special.htm
eygle的網站字符集問題專題帖。一共7篇文章,貌似發表在ITPUB出版物上。從字符集基本知識講到內部詳細處理,必看!
http://www.itpub.net/showthread.php?threadid=276524
jeffli73的字符集問題分析,包括了字符集的基礎知識和幾個實驗。幾個實驗如果能真正理解了,字符集問題也就很少能困擾你了。
http://www.itpub.net/showthread. ... =%D7%D6%B7%FB%BC%AF
重點在Toms_zhang的最后幾個回貼。雖然是主講JAVA環境下字符集轉換問題,但在理解ORACLE字符集轉化上頗有值得借鑒之處。而且現在也常遇到數據庫,基于JAVA的WEB服務攪和在一起的狀況,這就將字符集問題更加復雜化了。
因為有上面這些相關帖子,我這文章的內容算一些零碎知識的補充,加上自己實驗和一些理解上的心得。希望以后的朋友們遇到類似問題看了我的帖子(其實主要是看我上里的鏈接)就能自己搞定大部分狀況。
2.字符編碼歷史,和常見字符集簡介
很多是照搬 http://www.chedong.com/tech/hello_unicode.html 的,有興趣的可以看看原貼。
a.最基本的字符集
一個 character set (字符集)是一組符號和編碼,比如,大家計算機原理第一課學的ASCII字符集,包括的字符有:數字,大小寫字母,分號、換行之類的符號,編碼方式是用一個7bit表示一個字符 (A的編碼是65,b的編碼是98)。ASCII只規定了英文字母的編碼,非英文語言不能用ASCII編碼表示,為此,不同的國家,都為自己的語言做了編碼。這就有了字符集第一步的擴展:
b.英文和歐洲其他語言的單字節字符集(SingleByte Charsets):
ISO(國際標準化組織)制定了一個針對歐洲多國的字符集標準系列ISO-8859,可以將該系列的字符集都想象成一個:2^8 = 16 * 16 = 256個格子的棋盤,這樣所有的西文字符(英文)用這樣一個16×16的坐標系就基本可以覆蓋全了。而英文實際上只用其中小于128(/x80)的部分就夠了。利用大于128部分的空間的不同定義規則形成了針對其他歐洲語言的擴展字符集:ISO-8859-2 ISO-8859-4等。不同國家使用復合自己國家語言的字符集標準。所有這些國家的字符集都有公共交集ASCII。
在oracle 7.3.4這樣老的系統中,很常見的就是WE8ISO8859P1(Western European 8-bit ISO 8859 Part 1)。記得即使中文系統好多也用的這個,忘了是不是默認值了。
c.亞洲語言和雙字節字符集
漢字這么多,用這么一個256格的小棋盤肯定放不下,所以要區別成千上萬的漢字解決辦法就是用2個字節(坐標)來定位一個“字”在棋盤上的位置,將以上規則做一個擴展:
* 如果第1個字符是小于128(/x80)的仍和英文字符集編碼方式保持兼容;
* 如果第1個字符是大于128(/x80)的,就當成是漢字的第1個字節,這個自己和后面緊跟的1個字節組成一個漢字;
其結果相當于在位于128以上的小棋格里每個小棋格又劃分出了一個16×16的小棋盤。這樣一個棋盤中的格子數(可能容納的字符數)就變成了128 + 128 * 256。按照類似的方式有了簡體中文的GB2312標準,繁體中文的BIG5字符集和日文的SJIS字符集等,GB2312字符集包含大約有六仟多個常用簡體漢字。所有這些從ASCII擴展式的編碼方式中:英文部分都是兼容的,但擴展部分的編碼方式是不兼容的,雖然很多字在3種體系中寫法一致(比如“中文”這2個字)但在相應字符集中的坐標不一致,所以GB2312編寫的頁面用BIG5看就變得面目全非了。而且有時候經常在瀏覽其他非英語國家的頁面時(比如包含有德語的人名時)經常出現奇怪的漢字,其實就是擴展位的編碼沖突造成的。我把GBK和GB18030理解成一個小UNICODE:GBK字符集是GB2312的擴展(K),GBK里大約有貳萬玖仟多個字符,除了保持和 GB2312兼容外,繁體中文字,甚至連日文的假名字符也能顯示。而GB18030-2000則是一個更復雜的字符集,采用變長字節的編碼方式(2~4字節),能夠支持更多的字符。
oracle8,8i中,中文環境默認是ZHS16GB2312?還是ZHS16GBK?誰比較清楚滴?
c.橫空出世UNICODE
ASCII(英文) ==> 西歐文字 ==> 東歐字符集(俄文,希臘語等) ==> 東亞字符集(GB2312 BIG5 SJIS等)==> 擴展字符集GBK GB18030這個發展過程基本上也反映了字符集標準的發展過程,但這么隨著時間的推移,尤其是互聯網讓跨語言的信息的交互變得越來越多的時候,太多多針對本地語言的編碼標準的出現導致一個應用程序的國際化變得成本非常高。尤其是你要編寫一個同時包含法文和簡體中文的文檔,這時候一般都會想到要是用一個通用的字符集能夠顯示所有語言的所有文字就好了,而且這樣做應用也能夠比較方便的國際化,為了達到這個目標,即使應用犧牲一些空間和程序效率也是非常值得的。UNICODE就是這樣一個通用的解決方案。你可以把UNICODE想象成這樣:讓所有的字符(包括英文)都用2個字節(2個8位)表示,這樣就有了一個2^(8*2) = 256 * 256 = 65536個格子的大棋盤。在這個棋盤中,這樣中(簡繁)日韓(還包括越南)文字作為CJK字符集都放在一定的區位內,為了減少重復,各種語言中寫法一樣的字共享一個“棋格”。
d.為什么還要有UTF-8?
畢竟互聯網70%以上的信息仍然是英文。如果連英文都用2個字節存取(UCS-2),空間浪費不就太多了?所謂UTF-8就是這樣一個為了提高英文存取效率的字符集轉換格式:Unicode Transformation Form 8-bit form。用UTF-8,UNICODE的2字節字符用變長個(1-3個字節)表示:1. 對英文,仍然和ASCII一樣用1個字節表示,這個字節的值小于128(/x80);2. 擴展的ASCII字符(主要是西歐),第一字節用C2 - DF之間的范圍,雙字節表示。3.對其他語言,比如亞洲語系,還有各種特殊符號,使用3個字節表示;
因此,在應用中程序處理過程中所有字符都是16位(雙字節),但在存取轉換成字節流時使用UTF-8格式轉換,對于英文字符來說和原來用ASCII方式存取時相比大小仍然是一樣的,而對中文來說和原來的GB2312編碼方式相比,大小為:(3字節/2字節)=1.5倍
ps:UTF8標準也在變化。最新的標準,UTF8可以是1-4個字符。4個字符是為了將來可能的新字符,符號進行擴展。(用10g文檔上來說,就是:historic characters; musical symbols; mathematical symbols)為此oracle也定義了新的字符集AL32UTF8,也就是說其實UTF8的國際化標準就一個,只是版本在變化。而oracle公司根據不同國際標準版本有了UTF8和AL32UTF8兩個具體在數據庫上實現的字符集。UTF8不支持4字節擴展的。
總結:對于不同字符集關系的理解,我們可以用個比喻來說明。如果說一個字符集就是一個公司的倉庫,字符的編碼就是貨架編號,字符的實際值就是貨物本身。那么:
ASCII就是最小的倉庫,就是127個貨架,放了127樣貨物,比如 100號貨架上放的是雨傘;
西歐擴展iso-8859系列,這些倉庫都比A記(ASCII)多了差不多一倍的貨物,而且編號1~127的貨物也和A記一模一樣。不過如果你去看160號貨架的話,可能這家放的茶杯,那家就是毛巾了;
ZHS16GBK,這是個百貨公司 ,有上萬的貨物,即是如此還是沒有忘本,你如果找100號貨架,肯定還是能拿到雨傘;
UNICODE,他的口號是別人有的我也要有。所以修了有65535個貨架的大倉庫。雖然Z記有的東西他也全有,不過貨物放的貨架(編碼)全不一樣了。
UTF8,真正的托拉斯,不但做到了人無我有(包括了現在所有的字符集的字符),還想千秋萬代一桶漿糊(為以后擴展留了余地),和U記類似,東西非常多,貨物編號除了1~127號全和別人不一樣了。
3.oracle中字符集的轉化
常見有討論三種情況:
a. exp/imp時候
在這種情況下,可能存在四方字符集差異:源數據庫字符集、Export過程中用戶會話字符集、Import過程中用戶會話字符集、目標數據庫字符集。具體可能的轉化過程eygle帖子里面有講。就不多說了。記住一條:為了確保Export、Import過程中,Oracle字符集不發生轉換或正確轉換,最好在進行這個過程前,檢查一下源數據庫字符集與Export 用戶會話字符集是否一致,源數據庫字符集與目標數據庫字符集是否一致,目標數據庫字符與Import用戶會話字符集是否一致。如果能夠保證這四個字符集是一致的,則在Export、Import過程中,Oracle字符集就不用發生轉換。
b.客戶端會話輸入字符,最終進入服務器存儲的過程
這個在上面說的帖子里面也詳細講述了。我后面會有個實驗做進一步的細節補充說明。
c.SQLLDR時候
按SQLLOADER的原理來看,應該和字符集沒有關系。如果CLIENT和SERVER端不一致,查出來的漢字當然是亂碼,但實際上已經LOADER成功,只是顯示問題。
4.如何查看字符集
a.如何查看服務器的字符集
通過初始化文件InitXXXX.ora文件進行查看;
借助SQL語句查看: SELECT NAME,VALUE$ FROM SYS.PROPS$ WHERE NAME=‘NLS_CHARACTERSET’
有很多種方法可以查出oracle server端的字符集,比較直觀的查詢方法是以下這種:
SQL>select userenv(‘language’) from dual;
b、如何查詢dmp文件的字符集
用oracle的exp工具導出的dmp文件也包含了字符集信息,dmp文件的第2和第3個字節記錄了dmp文件的字符集。如果dmp文件不大,比如只有幾M或幾十M,在windows下面可以用UltraEdit打開(16進制方式),看第2第3個字節的內容,如0354,然后用以下SQL查出它對應的字符集:
SQL> select nls_charset_name(to_number('0354','xxxx')) from dual;
UNIX下面可以用FTP到WINDOWS下面用WINHEX或者UE看,特別注意FTP方式要BIN。據說winHEX比UltraEdit打開大文件更快。
如果dmp文件很大,比如有2G以上(這也是最常見的情況),用文本編輯器打開很慢或者完全打不開,可以用以下命令(在unix主機上):
cat exp.dmp |od -x|head -1|awk '{print $2 $3}'|cut -c 3-6
然后用上述SQL也可以得到它對應的字符集。
c、查詢oracle client端的字符集
在windows平臺下,就是注冊表里面相應OracleHome的NLS_LANG。還可以在dos窗口里面自己設置,比如:
set nls_lang=AMERICAN_AMERICA.ZHS16GBK,這樣就只影響這個窗口里面的環境變量。
在unix平臺下,就是環境變量NLS_LANG。使用命令 echo $NLS_LANG 查看
如果檢查的結果發現server端與client端字符集不一致,請統一修改為同server端相同的字符集。
總結:字符集子集向其超集轉換是可行的,如US7ASCII向WEISO8859P1轉換;而字符集超類向字符集子集進行轉換時,可能會損失部分數據。在理解了字符集轉化原則的基礎上,我們可以知道:1.極端的情況,即使完全不兼容的字符集,可能也不會有數據丟失!只包含英文字符數據的雙字節字符集可向單字節字符集正確轉換,如ZHS16GBK(English Only)向US7ASCII(因為實在沒有什么好丟失滴 )。2.編碼范圍相同的單字節字符集之間通常可以進行相互轉換。比如iso-8859系列。
請注意,這里所說的沒有數據損失,是指一種字符集A轉換成另一種字符集B之后,可以再從字符集B正確轉換成字符集A或字符集B能夠正確表示字符集A中轉換過來的數據。
ps:一個英文字母是一個字符,一個中文漢字是幾個字符呢?我們知道,os字符集是gbk環境下,一個中文漢字是雙字節字符,但它算做幾個字符與其數據庫字符集有關。如果數據庫字符集使用單字節US7ASCII,則一個中文漢字是二個字符;如果數據庫字符集使用雙字節字符集ZHS16GBK,則一個中文漢字被視為一個字符。有關這一點可以使用 Oracle的函數Substr得到證明。
使用US7ASCⅡ字符集時:
Select substr(‘東北大學’,1,2) from dual;
語句執行結果返回‘東’。
使用ZHS16GBK字符集時:
Select substr(‘東北大學’,1,2) from dual;
語句執行結果返回‘東北’。
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環境下是否也相對獨立?客戶端程序受環境變量影響的條件,估計還得具體環境具體測試才能確定了。
6.我對oracle轉化字符集意圖的理解,一些問題的回答
Q、oracle數據庫字符集要轉換的意義何在?為什么要如此的規則(根據服務器/客戶端字符集異同確定轉化字符集與否)轉換?
面對眾多使用各種五花八門字符集的客戶端,為了盡量減少信息丟失和混亂,只能盡力往服務器端統一。只不過由于目前字符集實在太多太亂,所以效果很不理想。猜測一下oracle如此規則的設計思想:當服務器和客戶端字符集設置一樣,他認為用戶可能想原樣保持初始的數據。所以不進行轉換。當設置不一樣,oracle會默認客戶輸入最初的字符所用的字符集應該和oracle客戶端程序的字符集一致。即OS到客戶端程序這步不應該有信息丟失。(其實這才應該是最普遍的情況,一般的用戶,誰會沒事主動改nls_lang之類的?)oracle做的,只是將客戶端程序的字符編碼改為服務器端字符編碼方式存儲。只要客戶端沒變化,將來客戶端讀取數據還是客戶希望的樣子。
當然,這樣的原則只是能適應上述理想狀況,現實中總會遇到種種意外。不過話又說回來,IT行業好象很難有什么包治百病的方案。或許,在不遠的將來,數據庫服務器端都采用UTF8(不考慮存儲浪費)能解決相當的客戶端程序到服務器之間信息丟失的問題。但誰知道,到時候又會有什么新問題出現?
Q.問題 http://www.itpub.net/showthread. ... =%D7%D6%B7%FB%BC%AF
http://www.itpub.net/showthread. ... =%D7%D6%B7%FB%BC%AF
A.記住2點:1.無論什么平臺,什么軟件,最終磁盤上存儲的是基本單位為一個字節的數據;2.所有要放到數據庫中的字符,不管什么編碼,最初都是從OS環境進去的。你測試的平臺,能同時輸入不同語言,那么OS級別肯定支持UNICODE,(我估計UTF16,因為IE支持正常顯示)進入數據庫的應該是UNICODE編碼,哪樣那怕ASC, GBK, UTF8數據庫,存的編碼都是哪些。只不過只有UTF8是"正確理解"上的存儲。如果客戶端只支持單一語言字符集,給客戶端的UNICODE編碼,但單一語種客戶端比如繁體,同樣的字編碼格式不同,出來肯定亂碼。
Q.有一點我不能理解。我數據庫字符集原為ZHS16GBK,然后改為WE8ISO8859P1。這時我查詢原數據(含中文字符),客戶端的用戶會話字符集為WE8ISO8859P1,但查出的中文卻不是亂碼,而是正確的。按理說,如果原來的數據字符集仍為ZHS16GBK,我查詢它,應該會發生字符集轉換。不知我哪里理解錯了?
A.因為服務器端,客戶端字符集設定一樣。所以不進行轉化,系統直接把舊的GBK中文當作數據傳送。WE8ISO8859P1是擴展的ASCII字符集,高位也顯示數據,客戶端直接拿傳過來的數據顯示。如果客戶端會話字符集設置為US7ASCII,就會顯示亂碼,但也只是顯示問題,服務器里面的數據是沒有變化的。
為了驗證一些字符集轉換的細節,我作了下面的實驗。為了節約版面,就不用給一條命令,顯示一條結果的方式了。下面列表中一條記錄各代表一種環境變量,運行程序的組合。表當中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環境下是否也相對獨立?客戶端程序受環境變量影響的條件,估計還得具體環境具體測試才能確定了。
6.我對oracle轉化字符集意圖的理解,一些問題的回答
Q、oracle數據庫字符集要轉換的意義何在?為什么要如此的規則(根據服務器/客戶端字符集異同確定轉化字符集與否)轉換?
面對眾多使用各種五花八門字符集的客戶端,為了盡量減少信息丟失和混亂,只能盡力往服務器端統一。只不過由于目前字符集實在太多太亂,所以效果很不理想。猜測一下oracle如此規則的設計思想:當服務器和客戶端字符集設置一樣,他認為用戶可能想原樣保持初始的數據。所以不進行轉換。當設置不一樣,oracle會默認客戶輸入最初的字符所用的字符集應該和oracle客戶端程序的字符集一致。即OS到客戶端程序這步不應該有信息丟失。(其實這才應該是最普遍的情況,一般的用戶,誰會沒事主動改nls_lang之類的?)oracle做的,只是將客戶端程序的字符編碼改為服務器端字符編碼方式存儲。只要客戶端沒變化,將來客戶端讀取數據還是客戶希望的樣子。
當然,這樣的原則只是能適應上述理想狀況,現實中總會遇到種種意外。不過話又說回來,IT行業好象很難有什么包治百病的方案。或許,在不遠的將來,數據庫服務器端都采用UTF8(不考慮存儲浪費)能解決相當的客戶端程序到服務器之間信息丟失的問題。但誰知道,到時候又會有什么新問題出現?
Q.問題 http://www.itpub.net/showthread. ... =%D7%D6%B7%FB%BC%AF
http://www.itpub.net/showthread. ... =%D7%D6%B7%FB%BC%AF
A.記住2點:1.無論什么平臺,什么軟件,最終磁盤上存儲的是基本單位為一個字節的數據;2.所有要放到數據庫中的字符,不管什么編碼,最初都是從OS環境進去的。你測試的平臺,能同時輸入不同語言,那么OS級別肯定支持UNICODE,(我估計UTF16,因為IE支持正常顯示)進入數據庫的應該是UNICODE編碼,哪樣那怕ASC, GBK, UTF8數據庫,存的編碼都是哪些。只不過只有UTF8是"正確理解"上的存儲。如果客戶端只支持單一語言字符集,給客戶端的UNICODE編碼,但單一語種客戶端比如繁體,同樣的字編碼格式不同,出來肯定亂碼。
Q.有一點我不能理解。我數據庫字符集原為ZHS16GBK,然后改為WE8ISO8859P1。這時我查詢原數據(含中文字符),客戶端的用戶會話字符集為WE8ISO8859P1,但查出的中文卻不是亂碼,而是正確的。按理說,如果原來的數據字符集仍為ZHS16GBK,我查詢它,應該會發生字符集轉換。不知我哪里理解錯了?
A.因為服務器端,客戶端字符集設定一樣。所以不進行轉化,系統直接把舊的GBK中文當作數據傳送。WE8ISO8859P1是擴展的ASCII字符集,高位也顯示數據,客戶端直接拿傳過來的數據顯示。如果客戶端會話字符集設置為US7ASCII,就會顯示亂碼,但也只是顯示問題,服務器里面的數據是沒有變化的。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
