一個字元有多種編碼方式,編譯器或直譯器須知道程式碼中的字元編碼,才能正確解讀程式,得知編碼方式可能是使用預設字元集、程式引數、環境或全域變數設定、在原始碼中使用魔法註解(Magic comments)等。

有些編譯器會使用作業系統預設編碼,讓開發者以為它能會聰明地分辨原始碼檔案編碼為何,例如Java。如果在中文Windows中,用記事本編輯純文字檔案,預設用MS950編碼;在Ubuntu使用vi編輯純文字,預設使用UTF-8;Java原始碼若含中文字串,編譯器在Windows中預設使用MS950解讀原始碼,在Ubuntu中預設使用UTF-8。

如果在Windows因為中文字串包括了「?」,記事本要求轉存為Unicode,而存檔時選「Unicode big endian」編碼,編譯時,也沒有指定-encoding告知檔案編碼為UTF-16,就會出錯。

有些編譯器或直譯器預設原始碼會使用特定字元集,通常是ASCII字元集,如果使用了範圍外的字元,必須明確告知編譯器或直譯器檔案編碼為何。例如,原始碼檔案撰寫中文而編碼為UTF-8時,Ruby 1.8必須指定$KCODE為'u',或在執行直譯器時,指定-Ku引數,而Ruby 1.9必須在檔案開頭撰寫# encoding: UTF-8;Python 2.x必須在檔案開頭撰寫# -*- coding: UTF-8 -*-,Python 3.x則預設使用UTF-8字元集,原始碼檔案撰寫時必須使用UTF-8編碼,也就不用在檔案開頭撰寫魔法註解。

網頁應用程式經常處理亂碼問題,最常面對的對象之一,就是JavaScript如何處理編碼問題。

現代瀏覽器會假設載入的.js編碼與HTML網頁編碼相同。如果.js檔案與網頁編碼不同,例如網頁編碼為Big5而.js檔案為UTF-8時,.js檔案中ASCII範圍以外的字元,就會有亂碼問題。如果.js檔案與網頁的編碼不同,可以在,瀏覽器才知道以UTF-8解讀.js檔。

若將HTML檔當作原始碼,瀏覽器如何知道檔案的編碼?可以傳送HTML前使用Content-Type標頭指定,如果是靜態網頁,可在

 

中使用指定,像是。執行時,採通用字元集或字集獨立處理?

瞭解原始碼檔案編碼,並正確告知編譯器與直譯器,只是第一步,接下來,得瞭解程式運行時採用通用字元集(UCS)或字元集獨立處理。

以Java為例,其採用通用字元集處理方式,執行時期字元的內部編碼(Internal Encoding)固定採用UTF-16 Big Endian,每個字元實際都是兩個位元組,字元不會有其他編碼資訊。

由於外部字元資料的編碼,可能與內部編碼不一致,採通用字元集的程式,都會提供編碼轉換方法,例如Java的String提供getBytes(),可將代表字元UTF-16編碼的位元組,轉換為指定編碼的位元組,使用Reader、Writer等類別處理字元輸入輸出時,也可指定外部編碼(External Encoding)資訊,將字元的位元組正確讀入,並轉換為UTF-16,或將UTF-16正確轉換為目的地編碼的位元組。

為了簡化通用字元集編碼轉換問題,這類程式在執行時期通常可指定預設外部編碼,例如,Java執行時,可指定系統屬性file.encoding,作為String的getBytes()方法,或FileReader、FileWriter等API預設的外部編碼轉換依據。

採字元集獨立方式處理,沒有外部與內部編碼轉換問題,字元集獨立方式下,字元在程式執行時只是原始的位元組集合。

例如Ruby 1.8採字元集獨立方式,原始碼若使用UTF-8撰寫的中文字元佔了三個位元組,那在程式運行時也是佔3個位元組,原始碼若使用Big5撰寫中文字元,在程式運行時就是佔2個位元組。

雖然沒有外部與內部編碼轉換問題,但實際上字元只是原始位元組集合,要完成計算字元長度或使用規則表示式(Regular expression)處理時較麻煩。

Ruby 1.9也採字元集獨立方式,但允許指定預設內部編碼與外部編碼,在讀取或寫入外部資料時,若沒有額外指定編碼,會使用預設內部編碼與外部編碼作為轉換依據,不過字串也允許擁有個別編碼資訊,而不一定要採用內部編碼。

雖然採字元集獨立方式,不過Ruby 1.9加強了編碼轉換API,像是透過字串取得長度時,傳回的是字元長度而不是位元組長度,編碼轉換也可以透過encode()等方法來完成。Ajax代表更多編碼系統間的轉換

Ajax代表多個系統之間的互動(瀏覽器跟JavaScript環境,JavaScript跟伺服器),這表示更多外部編碼與內部編碼間的轉換,若搞不清楚,亂碼就會出現。

舉例來說,開發者可從非同步物件的responseText取得伺服端回應文字,伺服端回應時,若沒有使用標頭指明編碼(例如Content-Type: text/html; charset=Big5之類),預設會使用UTF-8解讀傳回的文字,並設定給responseText。

在前一篇文章中談過URL編碼,如果透過表單發送請求,瀏覽器會自動進行URL編碼,並將空白字元編碼為+。如果透過Ajax以非同步物件發送請求,有的瀏覽器會進行URL編碼,有的瀏覽器不會,最保險方式還是自行處理URL編碼,再透過非同步物件發送請求。JavaScript可以使用encodeURIComponent()作字元編碼,結果大致上是編碼為%hexhex,不過空白字元是編碼為%20,而不是HTTP規範的+,所以程式中要再將%20取代為+,以符合HTTP規範。

JavaScript內部用Unicode處理字元,實作上採用UTF-8,只要在瀏覽器正確顯示,用JavaScript取得的就會是正確的字元,傳入encodeURIComponent()的字串是以UTF-8編碼,若將encodeURIComponent()後的URL編碼字串由非同步物件發送,伺服端須以UTF-8處理接收的URL編碼。

如果非同步物件的responseText收到了'犇',那要如何設定給文字輸入欄位呢?document.getElementById('input1').value = '犇'行不通,因為會在瀏覽器上直接看到犇,而不是「?」,這是因為處理HTML實體編號的是瀏覽器,不是JavaScript。不過,開發者可以使用innerHTML作弊(這個特性在HTML5終於標準化),指定給innerHTML的字串會被瀏覽器當作HTML剖析,實體編號也就會正確地被解釋為「?」,所以投機方式之一,就是建立一個隱藏的,將'犇'設給它的innerHTML,然後再取得它的innerHTML。

亂碼出在哪個環節?
有些開發者會有像是「系統全面採用UTF-8不就可以解決問題了?」的論調,問到什麼是UTF-8或為何要採用UTF-8,則無從回答。

「全面採用」也有些模糊,全面是指哪些程式?如何採用?開發者如果對系統中每個環節編碼沒有基本認識,如何得知全面採用要設定的地方?或某些選項設定後影響的對象?例如Rails 3.1預設採用UTF-8,那它當中哪些對象採用UTF-8?不是預設為UTF-8嗎?為何在.rb檔案寫個中文就爆炸了?

有時亂碼出現未必是整個系統,而是某個環節,此時應逐一確認哪個環節前是正確字元,哪個環節後開始發生亂碼。例如看到資料庫撈出的資料在主控台(Console)出現亂碼,會是在哪個環節出錯?瀏覽器送出的請求參數正確嗎?伺服端接收請求參數時正確嗎?將資料儲存至資料庫前正確嗎?或是資料庫編碼為UTF-8,而主控臺只能顯示Big5(這感覺就像是鍵盤沒反應,只是忘了接上插頭一樣)?某天在資料庫中發現的犇真的是亂碼嗎?還是會想到,表單網頁其實是Big5編碼呢?

作者簡介


Advertisement

更多 iThome相關內容