這一篇是個學習紀錄,對於字元編碼其實從前是有點知其然不知其所以然,今個兒決定要徹夜苦讀,爬了些文章並自已做了測試,寫下一些學習心得。可能觀念上還是有些問題,如果有高手路過,一定要給小弟指教指教。
先實作看看
二個是字元編碼的規範,先不談理論,我們實作一下,開啟Windows 裡頭的NotePad.exe 然後輸入「DNOWBA的部落格」
接著「另存新檔」,分別存二種編碼檔案:
再次打開這二個檔案,比較一下,沒有差別對吧。
分別在二個檔案ANSI.txt 、Unicode.txt 上滑鼠右鍵選內容,發現不一樣了嗎?二個檔案輸入「看起來」一樣的字,結果檔案大小竟然是ANSI格式的比較小。
為什麼前面測試要用「另存新檔」,直接儲存的話,不知道檔案是存成什麼格式的呢?
我們再開一次記事本,然後輸入「DNOWBA的部落格」,這次直接儲存吧,檔案名稱就叫Unicode or ANSI.txt,存完後在檔案上點右鍵內容,大小為14位元組…這樣你知道他存成什麼格式了嗎?
好了,這次我們再用記事本,輸入「鱻鱻鱻鱻鱻鱻鱻鱻」,直接儲存,注意到了,這個時候出現錯誤訊息:「這個檔案包含Unicode格式字元,如果您以ANSI 編碼的文字檔案來儲存這個檔案,將會遺失這些字元,若要保留Unicode資訊,請按下面的取消,然後從編碼下拉清單選擇一個Unicode選項」
為什麼會這樣呢?為什麼?為什麼?
實作到這邊先暫停,我們可以說一下理論了
ASCII 的產生:
我們在電腦前面看到的字,是有個字元呈現引擎(rendering engine)來實現的。1984的蘋果、1985的微軟相繼開啟了圖形化作業系統的大門,設計了字元呈現引擎來呈現不同的文字,這些文字依照被編寫在一個表,稱之為「字符集」,而字元呈現引擎可以支持不同的「字符集編碼」(有人叫它代碼頁、內碼表),這類的代碼頁叫做「ASCII代碼頁」。
在電腦世界,所有的數據在存取、運算都要使用「二進制」表示 ( 電腦用高電平、低電平分別表示 1 和 0 ,在電路中遊走交換訊息),所以在電腦上的字也是使用二進制,像a~z、A~Z、0~9 這些字,要用哪些二進制數表示,每個人都可以自已設定(這就叫編碼),但如果大家想要互相通信不造成系統間的混亂,就必須要使用相同的規則,於是乎美國的國家標準學會(ANSI)就制定了ASCII來做為標準。
後來這個ASCII標準也被國際標準化組織(ISO)定為國際標準,稱為ISO 646標準
是美國人制定的,當然系統剛開始時沒人會想到要讓電腦本地化,所以這套內碼表很簡單,字碼是適用於西方的拉丁語系。這邊略舉ASCII碼表的內容:
BIN | Dec 10進制 | HEX 16進制 | 字符 |
00000000 | 0 | 00 | NUL(null 代表空字符) |
00001000 | 8 | 08 | BS (backspace 代表倒退) |
00011000 | 24 | 18 | CAN (cancel代表取消) |
00100001 | 33 | 21 | ! |
00100101 | 37 | 25 | % |
00110011 | 51 | 33 | 3 |
01001000 | 72 | 48 | H |
01111010 | 122 | 7A | z |
01111111 | 127 | 7F | DEL (delete 代表刪除) |
如上圖,ASCII內碼表裡,除了基本的英文字母、數字、標點符號,還有一些給予做為控制用,像倒退、取消都提供給主機外的設備來使用。
這樣的表示法,是用高電平4位,低電平4位,共8位來表示二進制數,所以字元集最多也只能 有256個(2的8次方)。也就是這個ASCII內碼表最多容納256個符號。電腦讀的時候是BIN,編碼的時候方便起見用10進制來編流水號,這邊故意列出最前和最後,從0~127共128個字符欄位就夠用了。注意用位元來看的話,這第0號到127號的最高位(最左邊)都是0,所以扣掉最高位,7位元的ASCII就夠表示當時西方拉丁語系的符號了。
ANSI產生:
對於發明電腦的美國人來說,這些字符集很夠用了,不過隨著圖形作業系統被廣泛使用(WINDOWS 3.1,1991年),但到了面臨電腦全球化的時候,像中文正體字這種非拼音文字系統的國家來說,就是很大的問題了,所以系統上開發了字元繪製的功能,如果我們要在電腦上輸入中文,就勢必也要發展一套像ASCII這樣的內碼表,而容量上要非常大。
事實上在8位元的ASCII內碼表時,有人就覺得字符集後面其實還有128個空位(128-255)沒用到,反正一次就是傳1位元組,不用白不用,像IBM就用了(並命為OEM內碼表)這些空位來放線條繪圖字元之類的怪東東
電腦就只能用二進制數來傳資料是不爭的事實,太多人想把這個區域佔為已用,畢竟每個區域使用的文字符號不同,所以ASCII後期就出現了一些變體來處理這種語系的問題。亂了一陣子,這個亂用區總算在ANSI標準(微軟的標準,標準才能賺錢…在WINDOWS上被稱作"Windows (或ANSI) 代碼頁") 下固定下來了。大家(使用微軟系統的人)同意小於128的字元集定義就不動(就是前面的ASCII),而從128開始到255,視區域不同提供不同的字符集,這些不同的系統為了分辨,用不同的頁碼(代碼頁、code page,使用數字來標記)來識別。例如:
950 — 繁體中文(大五碼)
936 — 簡體中文(GBK)
1252 — 西歐拉丁字母 ISO-8859-1
這樣子算是解了燃眉之急了,一些拼音文字用後面的128空位算是措措有餘,不過,128以後的字符集是被多個代碼頁給重複使用的,所以不同語系的電腦,是「不可能」同時顯示多語系的語言,英語+法語 可以、英語加冰島語 可以,但冰島語+法語就不行。在不同國家間卻經常出現不相容的情況。很多傳統的編碼方式都有一個共同的問題,即容許電腦處理雙語環境,但卻無法同時支援多語言環境。
而到了漢字這種單體文字,128-255這128個空位很明顯的就是不夠,漢字的字集太大了,所以採用了雙位元組字元集(DBCS),以二個位元組來安放一個字,意思就是字集的容量上限變成了65536個(2^16) ,對於漢字來說勉強夠用,不過遇到一些不常用字「犇」「鱻」「堃」「喆」之類的字在這樣的系統下就打不出來。所以當時有字符集裡也有一個大家「默認」的造字區,windows也有個造字程式,讓你自已補缺少的字。
這也解釋了上面我們實作了「鱻鱻鱻鱻鱻鱻」存檔時碰到錯誤訊息的原因了,因為ANSI裡頭找不到他的位置。即然字集裡沒有這個字,我們怎麼打出來的???請耐心往下看。
我想和編字典一樣,這麼大量的字集編下來多少會有「重複」等錯誤的問題。而字元集中的某些字母是一個位元組來存,其他字則要用兩個位元組,這在編譯上也造成了向後移很容易、往前倒推字元的難度,形成「衝碼」的問題。
個人認為最慘的莫過於,因為國情的關係,漢字在不同的地區(中國、香港、台灣)的寫法不大一樣。在編碼上當時就被歸成不同套,所以產生了不同的內碼表,如GB2312、GBK、BIG5…
所以如果是繁體中文版的WINDOWS,和簡體中文版的WINDOWS,預設使用的字符集就是不同。繁體中文的WINDOWS 裝簡體軟體、開簡體文字會呈現亂碼…就是這種亂象
我們的狀況就是如此慘烈,明明開口說的都是中文,文字上雖然略有不同但也多能望文生義。頂多腔不同,卻被活生生切割,可憐的我們要負擔這樣的歷史共業。其他的拼音語系國家沒有這麼沉重的痛,對他們來說一個字還是只佔用一個位元組(8個位元)。而當時這種問題也沒有大多困擾,反正電腦就是一台一台的各自為政,只要不要把檔案搬到其他語系的電腦就沒問題。
對於現在的使用環境來說雖然不是一個很好的解決方案,但當時可是很合適偉大的設定,這也顯示出大家是想盡辦法為不同的語系來編碼。這樣的想法沒有停止,後來由民間團體促成的Unicode 編碼就此誕生了。這個時間點已經是WINDDOWS ME (WINDOWS 2000,2000年)後的事。剛好當時網路Internet興起,字串開始不是只在自已的電腦0101的動,開始也透過網路線在電腦間移動,竄來竄去的結果就竄出一堆火來,用中文語系的電腦上個日文網頁就都是亂碼…中國人逛中國人的網頁…竟然還會亂碼…中國人有這麼亂嗎?這真的會讓人一把火…
讀語文系的我,剛好經歷過那一段電腦歷史,真的很無言感慨,所以我覺得如果是要編寫程式的中國人,必須要了解這一個歷史,記取教訓,就是這種無聊的政治角力,讓二岸的知識學問無法交流。
剛好 Unicode 在那時已經成型的差不多了,成了名符其實的救火大隊…
Unicode 的產生:
Unicode 又叫統一碼、萬國碼、單一碼、標準萬國碼,目的就是要統一字符集,解決上述的問題。這個統一的方式很厲害,目標是把世界所有的符號都用單一個字元集(就是不要頁碼了),所有的書寫系統全部都在同一頁上,很偉大的目標,花了十幾來年,目前來說,很溫和的成長著。
我們可以用WINDOWS裡的Charmp.exe工具把unicode叫出來看一看 (windows2000/xp/7都有,現在知道為什麼是2000以後才有了嗎?)
如下圖,「 H」的unicode是U+0048,就算你換了字型(FONT),同一個符號的字碼還是一樣,「H 」還是 U+0048。這種想法沒什麼好爭的吧。
所以在漢字裡也是這樣,長得類似的字也是被歸為同一個字碼,我們繼續操作這個charmp字元對應表,例如愛這個字
上面三個圖是分別用日文漢字(平假名)、中文繁體注音符號、中文簡體拼音的方式找到的愛字,在意義上,三個字的意義是相同的,如果是之前的BIG5碼,表意文字系統的學者,就會刻意把這三個字放在同一字碼不同代碼頁上。不過到了想大一統的Unicode,大家都要編在同一張代碼頁上,所以就用長像來區分,日文、中文的愛是長得很像的,所以都被歸為U+611B,但簡體的愛長得不同,被放到U+7231去了。
U+在這裡是一個識別的符號,目的就是分辨這個字是什麼編碼,相當於十六進制數的「0x」,而U+後面的數字我只能說是透過code point 技術來編出的號碼(有人叫他魔法數字」,不是十六進制數,上圖中括號裡的才是十六進制,只要是Unicod的字集就一定會有0x,字集中的每個字都一樣有前綴字「U+」,所以也可以不看作是字集編碼的一部分,像上面的愛,簡單的說就是編在88A4這個位置,所以還是雙位元組編碼的方式。
Unicode 的編碼方式也是 16 位,理論上一共最多可以表示216(即65536)個字元,但用了「輔助平面」的方式,使得可以涵蓋一切語言使用的符號。
有關 Unicode 編碼方式相關的內容,老實說我已經「很多」看不懂了,我只知道中間有個code point 的技術,而且Unicode是目前最好的編碼方案,下面可能理解上有錯,請小心= =
我們拿工具UltraEdit來檢視一下文字符號在各編碼格式下的長像好了:
用UltraEdit軟體打開剛剛建置的ANSI.txt,看看他十六進制的情形
換個方式寫好了,底下是ANSI.txt注意二件事:
◎中文字「的、部、落、格」的代碼已經完全改變的,不變的是不管用ANSI還是Unicode都是十六進制數。
◎英文字裡,Unicode維持一貫的體系,為英文字、數字、拉丁語系的常用的標點符號,不過保留是保留,卻都被強制在低八位加上00。
UTF-8 和 UTF-16
上面這種編碼法,是Unicode的本意,據說是要解決不同系統開發廠商系統存取資料的方法,就把所有的符號一律改成16進制數,沒用到也得用0來補…至於要解決系統開發廠商什麼問題,用Mac和Microsoft二個開發系統來說好了,二個系統對字節的理解順序是不同的,Mac 系統主要是 Big Endian(BE), PC 系統則是使用 Little Endian(LE)。是電平讀取順序的問題,為什麼Mac要先處理高電平,再處理低電平…為什麼微軟就要用LE,先處理低八位再處理高八位,為什麼二個不統一一下呢?說真的只有他們自已知道。
以上面的文字字串「部落格」來說,
若是用LE,那麼Unicode存到記憶體是 E8 90 3D 84 3C 68
若是用BE,那麼Unicode存到記憶體會變成 90 E8 84 3D 68 3C
這樣子的話二個系統間交換資料時不就大亂
所以設計Unicode的人又被迫想一些厲害有趣的方式來解決問題,因為Unicode存取任格字符都是用二個字節的空間,所以為了解決BE和LE的問題,每個Unicode字串的開頭存一個FE FF,稱之為Unicode Byte Order Mark,簡寫就稱作「BOM」,中文叫中文叫「位元組順序記號」,如果因為系統不同,高低位元組對調,標記就會變成FF FE,讀字串的人就知道其他位元組都要對調。其他的字碼就可以反向得到正確的資料了。
我們用UltraEdit軟體再次打開Unicode.txt,前面的二個位元組就紀錄了所謂的BOM , FFFE 。這是軟體在儲存檔案時自已偷偷加進去的。(當然不是每個軟體開發者一定都會做BOM的機制來存Unicode)
這個方式感覺很理想,不過在實際使用上,卻被一些程式設計者抱怨,抱怨的無非就是以前用「44」一個位元組就可以得到「D」這個字,現在要用「4400」二個位元組,看看那些多出來的0,英語語系國家只會覺每個字元佔用2個位元組太浪費了,因為英語、數字、符號這些在ASCII 只佔了1Byte,ANSI 也保留了0-127位供ASCII使用,所以本來用16位元的Unicode ,出於節省的目的,也就發展了Unicode轉換格式(Unicode Transformation Format,簡稱為UTF)
如果一個僅包含基本7位ASCII字元的Unicode文件,每個字元都使用2位元組的原Unicode編碼傳輸,其第一位元組的8位始終為0。這就造成了比較大的浪費。對於這種情況,可以使用UTF-8來進行演算,將字元轉換成一種可長可短的編碼,這樣可能節省大量的容量。對於網際網路傳輸資料尤其節省頻寬,所以成了電子郵件、網路檔案傳送最愛的一種編碼格式。
標準的Unicode是使用16位,而透過UTF-8轉出來的字符,如果是英語、數字…等,就會被轉成8位,其他的字才是16位;為了區別這個差別,有人會稱Unicode是UTF-16,其實UTF是沒有在轉換16位的,因為Unicode本來就是16位,不需要再轉換,所以Unicode基本上就是UTF-16。
UTF-8對於英語系的國家來說是很好的節省方式,對於中文來說呢?我們再把TXT檔拿出來說明一下檔案大小的問題。
ANSI:
字串:D N O W B A 的 部 落 格
大小:1 + 1 + 1 + 1 + 1 + 1 + 2 + 2 + 2 + 2 =14 位元組UTF-16:
字串:D N O W B A 的 部 落 格
大小:2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 = 20 位元組
和上面的Unicode.txt的圖對照,怎麼內容顯示的是22位元組?因為BOM的FFFE佔二個位元組啊!,所以20+2=22
UTF-8節省了空間,不過因為一下長一下短的編碼反倒是花費演算的時間,降低了編碼的效率問題。為了解決BE和LE的問題,不知道哪個鬼才搞了個BOM,在UTF-8上面還是一樣,至今仍然有的軟體,還是在我們不注意的情況下加上BOM,如下圖 UTF-8 就是EF BB BF…這次還有三個字元。
不過還好的是UTF-8並不像UTF-16 那樣,一定要加上BOM檔頭,UTF-8因為在資料的判讀比較容易,用「逐一比對」的機制可以判斷出來所寫的內容是ANSI編碼還是UTF-8編碼(例如: 連續3個byte為 [E0~EF] , [80~BF] , [80~BF] 或 連續2個byte為 [C0~DF] , [80~BF] 就一定是UTF-8 的文件),所以也可以存成檔頭不含BOM表的UTF-8格式。
前面提到這個UTF-8是節省了空間,如果在網路發達的現在來說是很利於訊息傳輸的,但對使用漢字的中文Chinese、日文Japan、韓文Korea 的人來說可能就不大樂觀了,怎麼轉換的我不知道,竟然把漢字集搞到3個位元組了。下圖是表示UTF-8和UTF-16在字符大小的比較,漢字在欄位上是CJK,UTF-8佔三個位元組。
那麼對使用中文的我們來說,這樣子反而浪費了空間了,沒錯,所以我們的硬碟就是要買的比外國人大顆,怎麼國外的APPLE手機來台灣後,台灣一定買最高容量…國外16G很夠用了,我們的中文因為UTF-8的關係,消耗的內存就是比人家多。
不過網路世界不能這麼算,因為雖然我們看到的是中文網頁,都是中文字的情況下讓我們想用UTF-16來節省一下,但實際上,網頁背後執行的英文程式也得算進去,一篇文章算好了,是中文多還是英文多呢?表音文字和表意文字的差異這個時候也看出來了,所以就讓外國人佔便宜吧,在網頁上改成UTF-16的人,反而會讓檔案變大。
(相較於網路程式,支持多國語言的單機軟體開發商愛用的是UTF-16喔)
好了,我們來算算檔案大小吧
UTF-8:
字串:D N O W B A 的 部 落 格
大小:1 + 1 + 1 + 1 + 1 + 1 + 3 + 3 + 3 + 3 + 3 (BOM佔的Byte) = 21
BOM BOM BOM
以前有人在寫PHP程式的時候,經常會發生一個問題,就是在INCLUDE文件時,在前台顯示時會多了一條縫隙或空格,導致版面大亂,不管怎麼修改HTML或CSS都沒有用,這個時候,你可能就是踩到地雷 BOM 了。
歸納前面的理論,Unicode 格式是大家最愛使用的,而實現的方式 可以是UTF-16 或是後面的 UTF-8 ,UTF –16 因為字節都是2個位元組,在辨別(演算)難度上的關係在檔頭加了FFFE做為BE、LE系統的讀取順序辨別。到了UTF-8 因為字符編碼長度不一,相對辨別度變的較高,所以BOM不加在表頭也很容易透過軟體程式去識別。不過舊有的BOM支持者還是在UTF-8上加了EF BB BF,這個BOM不是用來區別先讀高八位還低八位的問題,唯一的價值只是告訴別人,這個檔案用的是UTF-8格式,謹此而已。
網際網路時代,W3C是沒有在承認這種用了utf-8還加bom的怪異行徑,因為網際網路可是穿梭在世界各個角落,大於127的有上百種的編碼方式,想猜都猜不到,這時候的發展,網頁上有個位置,可以自已承認自已的網頁用了什麼編碼格式,不用別人的瀏覽器一直猜一直猜你是誰。例如:
電子郵件email:電子郵件來說,郵件表頭應該會有一個字串
Content-Type: text/plain; charset="UTF-8"
網頁:HTML檔案的Content-Type放在HTML檔案裡
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">網頁的meta tag,一定要放在<head>段落非常前面。因為網頁瀏覽器一看到這個tag就會停止分析,然後改用指定的編碼方式重新解析整個網頁。
所以在寫程式中發生這種問題的,大概都是踩到了bom,原因可能是編寫程式的軟體出了問題,最典型的是,以前在人在windows上架apache,然後放php網頁,要修改直接拿微軟的notepad改,一存成utf-8就中了bom了,微軟的notepad會在存檔時幫你把bom塞到檔頭去…
參考文章:
http://zh.wikipedia.org/wiki/ASCII
http://zh.wikipedia.org/wiki/%E5%A4%A7%E4%BA%94%E7%A2%BC#.E5.AD.97.E7.AF.80.E7.B5.90.E6.A7.8B
http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E9%9B%86
http://zh.wikipedia.org/wiki/Unicode
http://zh.wikipedia.org/wiki/UTF-8
謝謝 寫得很仔細! 前陣有遇到過Bom不過不是在寫程式 而是在用cue檔的時候常常會亂碼,現在終於懂了Bom是什麼東東 哈哈
回覆刪除謝謝你的鼓勵,只是寫出我知道的,很高興也能解決你的問題。
刪除謝謝你的鼓勵,只是寫出我知道的,很高興也能解決你的問題。
刪除這篇真是太受用了,寫的超詳細。
回覆刪除我有個記事本用左unicode格式來存儲(因為裡面有D簡體字)
回覆刪除我有點原因 我洗機了
我又忘記copy個記事本
那我洗機之後就用《安易数据恢复软件》
將個記事本恢復 回來
但弄回來之後就一大堆亂碼
我想知道有沒有辦法弄好那些亂碼呢
變回原本的樣子
PS:
不是unicode格式的記事本 恢復后是正常的(那些字都正常可以看到的,跟之前一樣)
就是unicode格式的記事本就變了亂碼看不到
雖然不能解決我面臨的問題,但就觀念上很實用
回覆刪除感謝你的撰寫,寫得非常詳細
回覆刪除寫的真棒
回覆刪除寫的真棒!!
回覆刪除深入淺出,感謝分享
回覆刪除寫得詳細又好懂 謝謝!
回覆刪除寫的真的很棒, 受益匪淺 感謝大大!!
回覆刪除謝謝分享 但是BE LE的例子好像寫反了?
回覆刪除很專業, 很用心, 給你大大讚
回覆刪除