由web程序出現(xiàn)亂碼開始挖掘(Bom頭、字符集與亂碼)從第一次開始寫web程序,自己還有身邊同事開發(fā)出現(xiàn)亂碼情況基本都沒有消停過。估計以后還會一樣繼續(xù)。這么些年,不斷修修改改,也總結也歸納。程序從asp,asp.net,jsp,php,服務器從windows到linux,數(shù)據(jù)庫也從sqlserver,mysql到oracle;它還是偶爾會出現(xiàn)。 好了,我總結下我與它較量的一些收獲吧。亂碼都與字符集有關系,一切都從它開始說。
什么是字符集,什么是字符編碼,它做什么用?
字符(Charcter)是文字與符號的總稱,包括文字、圖形符號、數(shù)學符號等。而字符集是一組抽象的字符組合的集合。如:英文字符集,中文字符集,日文字符集等
什么是字符編碼?
計算機只能存儲0,1之類2進制數(shù)字,怎么樣讓它表示那么多各種各樣的字符呢?就需要對各種字符指定一個數(shù)值的編碼代號它就是字符編碼。如:a這個字符,在ascii字符集編碼表中對應的編號是97,而“中”在gb2312字符集中對應的編號是:16進制是D6D0 10進制是54992。通過編號就可以找到計算機對應字符。不用將那么復雜的字符保存在計算機中,只需要保存它代號就好。字符集只是指定了一個集合中有哪些字符,而字符編碼,是為這個集合中所有字符定義個編號,這就是字符集與編碼區(qū)別所在。
如果我告訴別人,我這個字符是:gb2312字符集中編號是:54992或者是D6D0 ,無論那個程序都知道是”中”,如果有人聽錯了,把它弄成日文JIS字符集,然后他也去找編號是:54992對應的字符,卻找到的是:”面“。
打了這個比方,相信大家找到原因了,同樣如果把54992拿到ascii 碼表找,就會得到對應:?? 兩個不能打印字符了。
從上面看,當你拿到本來是gb2312編號,在不是它的字符集里面找就出現(xiàn)這樣問題了。 其它,程序出現(xiàn)亂碼也都是這個原因,找錯了字符集表了.
字符在計算機是怎么樣存儲的呢?
看了上面介紹,我想大家一定會說,如果所有文本的字符,都用它的符號存儲在計算機里面,不就什么問題都么有嗎? 以前也這么想,后來一想啊,如果都存在計算機中,各種各樣,怎么樣表示呢?計算機處理01之類數(shù)字該多方便呢。
我們可以通過winhex實際來檢查下,下面在簡體中文下,將”中按照gb2312字符集編碼保存。
以gb2312編碼保存中文“中”,實際存儲在計算機中是:D6D0,是“中”在字符集gb2312中的編號啦!
計算機中只保存字符在某字符集中對應的字符編號值,計算機只需要維持一份字符集清單,當讀到這種編號值(編碼),就在對應字符清單中找出該字符顯示出來即可。字符大小顏色都是程序按照字符樣式繪制而成的。
看個圖:
計算機中只保存該字符在某字符集中對應的字符編號(也叫字符編碼)
怎么樣讀取文件并正確顯示文件內容?
從上面例子知道字符實際以該字符在某字符集中字符編碼存儲與計算機磁盤中。
下面以“中國”為例(中文簡體windows):
"中國" 保存編碼 存儲內容 記事本打開
ANSI D6D0 B9FA 正常
gb2312 D6D0 B9FA 正常
utf-8+BOM EFBBBF E4B8AD E59BBD 正常
utf-8 E4B8AD E59BBD 正常
unicode+BOM FFFE 2D4E FD56 正常
unicode 2D4E FD56 亂碼(變成ANSI)
EUC-JP C3E6 B9F1 亂碼(變成ANSI)
iso-8859-1 3F 3F 亂碼(變成ANSI)
以下以:“abc”為例
"abc" 保存編碼 存儲內容 記事本打開
ANSI 61 62 63 正常
gb2312 61 62 63 正常
utf-8+BOM EFBBBF 61 62 63 正常
utf-8 61 62 63 正常
unicode+BOM FFFE 61 62 63 正常
unicode 61 62 63 正常(變成ANSI)
EUC-JP 61 62 63 正常(變成ANSI)
iso-8859-1 61 62 63 正常(變成ANSI)
從上面可以得到幾點:
1、以某字符集存儲字符時,它會在該字符集中搜索這個字符的位置(編號或編碼),以這個編號(編碼)保存在文件,如果所搜找不到該字符,一般會以:3F 3F保存(一定會出錯)
2、當需要顯示字符,從取文件中字符編碼,如果沒有指定字符集,會通過文件頭(主要unicode字符集有特殊頭標記)判斷字符集,如果不是unicode字符集,默認都以ANSI字符集讀取,用字符編碼在該字符集中尋找對應字符,如果搜索到就正常顯示,搜索不到就會顯示亂碼.
常見問題字符疑問收集
什么是ANSI字符集?
這個不是固定字符集,如果在中文簡體windows中,它代碼字符集是gb2312,在繁體值代表是big5等等。
為什么英文字符不會出現(xiàn)亂碼?
常見ascii碼字符集是:128字符,對應編碼值是:1-128,二進制表示是:00000001-01111111。它表示了所有常見英文數(shù)字,標點符號。其它字符集都是由ascii碼字符集擴展而來,擴展了最高位由10000000開始,用多字節(jié)表示新的字符,基本都保留了:0xxxxxxx 開頭128個基本字符,而且對應編碼與ascii碼相同。
這樣,常見英文字符不論在那種字符集中,對應字符編碼一致,存儲編碼也一樣。讀取時候無論用什么字符集讀取,它所對應字符也一直。所有基本不會出現(xiàn)亂碼情況。
讀取軟件能夠識別存儲文件的字符集嗎?
由于目前各種字符集加起來有上百種,目前除了unicode字符集,定義的存儲文件頭,基本其它字符集只是給出了對應的字符編號值。因此,相同編號會出現(xiàn)在不同的字符集中,光從文件存儲的編碼值,是不能確定它的字符集的。如:gb2312字符集中,D6d0對應是“中”,而同樣是:D6D0在ecu-jp 字符集中,對應是“面”
什么是bom頭?
bom全稱是:byte order mark,漢語意思是標記字節(jié)順序碼。只是出現(xiàn)在:unicode字符集中,只有unicode字符集,存儲時候,要求指定編碼,如果不指定,windows還會用默認的:ANSI讀取。常見的bom頭是:
UTF-8 ║ EF BB BFUTF-16LE ║ FF FE (小尾)UTF-16BE ║ FE FF (大尾)UTF-32LE ║ FF FE 00 00UTF-32BE ║ 00 00 FE FF
unicode與utf-8 、utf-16 utf-32是什么關系?
unicode(統(tǒng)一碼、萬國碼、單一碼)是一種字符集,Unicode是國際組織制定的可以容納世界上所有文字和符號的字符編碼方案。Unicode用數(shù)字0-0x10FFFF來映射這些字符,最多可以容納1114112個字符,或者說有1114112個碼位。UTF-8、UTF-16、UTF-32都是將數(shù)字轉換到程序數(shù)據(jù)的編碼方案。在Unicode中:漢字“字”對應的數(shù)字是23383。我們可以用:UTF-8、UTF-16、UTF-32表示這個數(shù)字,將數(shù)字23383存儲在計算機中。UTF-8對應是:0xE6, 0xB1,0x89(3個字節(jié)),UTF-16對應是:0x6c49(2個字節(jié)),UTF-32對應是:0x6c49(4個字節(jié))。utf-8,utf-16,utf-32是unicode碼一種實現(xiàn)形式,都是屬于unicode編碼。
unicode編碼特點是什么?
unicode編碼特點是,它定義了編碼方式和存儲實現(xiàn)方式。編碼方式就是上面說的可以用,utf-8…utf-32表示,而存儲實現(xiàn)方式,無論那種編碼都知道了文件頭(bom)。因此,可以通過這個特殊頭來判斷存儲的文本文件使用那種字符集編碼。 為什么utf-8編碼不指定bom頭(可以理解為文件頭),軟件任然可以正常判斷出它字符集編碼?這個問題估計很多朋友都會產生疑問,為什么utf-16不指定就讀亂碼,而utf-8可以。我們可以從下面的例子看下: utf-8是怎么樣從unicode轉換而來了。 Unicode編碼(16進制) ║ UTF-8 字節(jié)流(二進制)000000 - 00007F ║ 0xxxxxxx000080 - 0007FF ║ 110xxxxx 10xxxxxx000800 - 00FFFF ║ 1110xxxx 10xxxxxx 10xxxxxx010000 - 10FFFF ║ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 從上面看,發(fā)現(xiàn)規(guī)律沒有?第一個自己開頭有幾個”1”,后面就對應有幾個10開頭字節(jié)了。 這樣我們都可以通過正則進行檢測了.
[\x09\x0A\x0D\x20-\x7E] # ASCII
|[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
|\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
|\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
|\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
|[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
|\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
由于它獨特的編碼存儲特點,因此目前常見文本處理軟件就能夠自動分析出來。(windows記事本,editplus,notepad++等)
為什么bom頭會產生亂碼?
有bom頭的存儲或者字節(jié)流,它一定是unicode字符集編碼。到底屬于那一種(utf-8還是utf-16或是utf-32),通過頭可以判斷出來。由于已經說過utf-16,utf-32不指定bom頭,解析程序默認就認為是ansi編碼,出現(xiàn)亂碼。而utf-8指定或者不指定程序都可判斷知道對于的字符集編碼。問題就出在這里,可能有的應用程序(ie6瀏覽器),它就認為如果utf-8編碼,就不需要指定bom頭,它可以自己判斷,相反指定了bom頭,它還會出現(xiàn)問題(因為它把頭當utf-8解析出現(xiàn)亂碼了)。這里不截圖了,cnblogs里面談這個比較多,目前ie6會出現(xiàn)問題。其它ie7+,firefox,chrome不會出現(xiàn),會忽略掉bom頭。統(tǒng)一解決辦法是:存為utf-8編碼是,不需要加入bom頭,其它utf-16,utf-32加入。
通過程序運算gb2312編碼能夠自動轉換為utf-8編碼嗎?
utf-8實際是unicode字符集表現(xiàn)方式。如果看了這2種字符集編碼表就清楚了。它是2個獨立字符集,相同漢字在2個字符集中所對應編號沒有關系,而且漢字順序也不同,gb2312先按照拼音后按照筆畫排序,而unicode沒有做相應規(guī)定。我們清楚知道,如果沒有對應字符集映射關系表在手。通過直接程序進行運算是實現(xiàn)不了的。如果你手里有這2個字符集映射表。如:”字”utf-8是:0xE6, 0xB1, 0x89,對應unicode編碼是:23383,然后拿23383,在unicode字符集尋找,發(fā)現(xiàn)是字符“字”,接著將“字”這個字符,拿到gb2312表中查詢:0xCE,0xC4 因此轉換結果是:0xE6,0xB1,0x89 ---> 0xCE,0xC4。
GB2312、GBK、gb18030、Big5是什么關系?
GB2312:1980年的GB2312一共收錄了7445個字符,包括6763個漢字和682個其它符號。漢字區(qū)的內碼范圍高字節(jié)從B0-F7,低字節(jié)從A1-FE,占用的碼位是72*94=6768。其中有5個空位是D7FA-D7FE。
GBK: 漢字國標擴展碼,基本上采用了原來GB2312-80所有的漢字及碼位,并涵蓋了原Unicode中所有的漢字20902,總共收錄了883個符號, 21003個漢字及提供了1894個造字碼位。包括港、臺兩種漢字字庫.
GB18030-2000產生,在GBK漢字標準字符集繼續(xù)擴展,GB18030是GBK的超集,也就是包含的字符要比GBK多,又增加了6351個字符,其中一部分為4字節(jié)字(four-byte encoding range)。增加了六種少數(shù)民族語言和一些四字節(jié)字。
Big5是中國臺灣的,是繁體中文代表
GB18030兼容GBK兼容GB2312,相同常用漢字在GB2312編碼表中字符編號(編碼)與GBK,GB18030相同。如:”字“gb2312字符編碼是:0xCE,0xC4,它在其它2個里面也是這個。因為GB2312只有7000多常用漢字,當出現(xiàn)繁體,古文時候就會出現(xiàn)問題,因此采用大集合的GB18030是個不錯選擇。
Big5與GB2312不能通過程序相互轉換,需要有字符集映射關系表才能完成。
字符集是怎么樣一個演變過程呢?
這個如果講故事可以講很久了。當計算機有美國人發(fā)明后,當時設計到字符輸入,由于是英文字符,通過收集整理。它們形成了標準的ascii碼(128) 字符集。8位,首位為0。由于不斷普及,歐洲西方國家相應使用,發(fā)現(xiàn)有些特殊字符它們不能表示,如:λφ等。如是出來想法,想利用ascii碼后128位,增加它們的字符。這樣就出現(xiàn)了EASCII碼。這些還是不能表示所有國家,想法語,俄語等有自己特殊字符。因此制定標準將后128位進行分片制定。制定出iso-8859系列字符集。
ISO/IEC 8859-1 (Latin-1) - 西歐語言
ISO/IEC 8859-2 (Latin-2) - 中歐語言
ISO/IEC 8859-3 (Latin-3) - 南歐語言。世界語也可用此字符集顯示。
ISO/IEC 8859-4 (Latin-4) - 北歐語言
ISO/IEC 8859-5 (Cyrillic) - 斯拉夫語言
ISO/IEC 8859-6 (Arabic) - 阿拉伯語
ISO/IEC 8859-7 (Greek) - 希臘語
ISO/IEC 8859-8 (Hebrew) - 希伯來語(視覺順序)
ISO 8859-8-I - 希伯來語(邏輯順序)
ISO/IEC 8859-9(Latin-5 或 Turkish)- 它把Latin-1的冰島語字母換走,加入土耳其語字母。
ISO/IEC 8859-10(Latin-6 或 Nordic)- 北日耳曼語支,用來代替Latin-4。
ISO/IEC 8859-11 (Thai) - 泰語,從泰國的 TIS620 標準字集演化而來。
ISO/IEC 8859-13(Latin-7 或 Baltic Rim)- 波羅的語族
ISO/IEC 8859-14(Latin-8 或 Celtic)- 凱爾特語族
ISO/IEC 8859-15 (Latin-9) - 西歐語言,加入Latin-1欠缺的芬蘭語字母和大寫法語重音字母,以及歐元(€)符號。
ISO/IEC 8859-16 (Latin-10) - 東南歐語言。主要供羅馬尼亞語使用,并加入歐元符號。
這些在一段時間,可以解決西方國家常見字符。當后來電腦在中日韓等國家普及時候,象中國常見漢字有7000多個,擴展128個空位,完全不夠。因此,需要用多個字節(jié)表示。后來就定,第一個字節(jié),第一位如果是1,后面還有一個字節(jié)與之一起表示一個字符。如果是0,就對應ascii碼。這樣就形成了國內的gb2312,后來還是不夠表示繁體中文,加入了:gbk,最后是gb18030,但是,這樣全世界各個國家還是用它們自己字符集進行表示。沒有一個統(tǒng)一的大字符集,能夠表示全球所有字符。直到unicode出現(xiàn),它的設計最多可以表示100多萬個字符。全球所有字符都可以收納在其中。寫出的程序,不用經常進行各種編碼轉換。就可以讓世界上所有國家可以閱讀對應字符文字。
什么是代碼頁,它與字符集有什么關系?
大家在指定網頁程序語言生活,還記得cp936表示中文代碼頁(code page)。那么它與我們說的gbk字符集有什么關系呢?代碼頁是字符集編碼的別名,也有人稱"內碼表"。早期,代碼頁是IBM稱呼電腦BIOS本身支持的字符集編碼的名稱。
常見字符集與代碼頁直接映射是:
cp charset
932 — 日文
936 — 簡體中文(GBK)
949 — 韓文
950 — 繁體中文(大五碼)
1200 — UCS-2LE Unicode 小端序
1201 — UCS-2BE Unicode 大端序
65001 — UTF-8 Unicode
936就是我們的gbk字符編碼集。
CJK字符集是什么?
cjk代號意思是:漢語(Chinese)、日語(Japanese)、韓語(Korean)。也就是包含這3國語言的字符集。包含這3個國家常見的漢字,一共有2萬多個。
所有軟件都會默認是:ANSI字符集嗎?
不同程序默認讀取字符集不同,上面舉例是記事本默認是這樣的。在php里面會以:iso-8859-1讀取。 jsp程序,java默認字符集也是:iso-8859-1。
為什么很多軟件程序編譯過程使用是:iso-8859-1字符集?
由于我們通過應用程序書寫的軟件是用各種字符集編碼保存在磁盤中。如果中文默認是用gbk。保存于磁盤中,默認以字節(jié)碼保存,是否有無中文,每個字碼值在:1-256。這些剛好可以用iso-8859-1擴展單字節(jié)字符集。因為,它是存儲最小單元。在程序語言里面,不會用中文作變量,函數(shù)等名稱。那些對應都是常見ascii碼。而中文用字注釋,或者一些常量中。在編譯時候不會影響到語法問題。它會以原來字節(jié)碼保持原樣。在讀取時候,只需要用對應的字符集編碼讀出,就能得到對應中文字符。